API CHANGE: moving XGen::Mongo::Driver and XGen::Mongo to Mongo and XGen::Mongo::GridFS to GridFS

This commit is contained in:
Mike Dirolf 2009-08-20 10:50:48 -04:00
parent 040ba7c9c2
commit e65dd99667
63 changed files with 2291 additions and 2423 deletions

View File

@ -7,9 +7,9 @@ many more.
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
db = Mongo.new('localhost').db('sample-db') db = Mongo::Mongo.new('localhost').db('sample-db')
coll = db.collection('test') coll = db.collection('test')
coll.clear coll.clear
@ -79,8 +79,8 @@ Here is some simple example code:
require 'rubygems' # not required for Ruby 1.9 require 'rubygems' # not required for Ruby 1.9
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
db = Mongo.new.db('my-db-name') db = Mongo::Mongo.new.db('my-db-name')
things = db.collection('things') things = db.collection('things')
things.clear things.clear
@ -149,8 +149,8 @@ using a PK factory lets you do so.
You can tell the Ruby Mongo driver how to create primary keys by passing in You can tell the Ruby Mongo driver how to create primary keys by passing in
the :pk option to the Mongo#db method. the :pk option to the Mongo#db method.
include XGen::Mongo::Driver include Mongo
db = Mongo.new.db('dbname', :pk => MyPKFactory.new) db = Mongo::Mongo.new.db('dbname', :pk => MyPKFactory.new)
A primary key factory object must respond to :create_pk, which should take a A primary key factory object must respond to :create_pk, which should take a
hash and return a hash which merges the original hash with any primary key hash and return a hash which merges the original hash with any primary key
@ -164,7 +164,7 @@ Here is a sample primary key factory, taken from the tests:
class TestPKFactory class TestPKFactory
def create_pk(row) def create_pk(row)
row['_id'] ||= XGen::Mongo::Driver::ObjectID.new row['_id'] ||= Mongo::ObjectID.new
row row
end end
end end
@ -178,7 +178,7 @@ ActiveRecord-like framework for non-Rails apps) and the AR Mongo adapter code
def create_pk(row) def create_pk(row)
return row if row[:_id] return row if row[:_id]
row.delete(:_id) # in case it exists but the value is nil row.delete(:_id) # in case it exists but the value is nil
row['_id'] ||= XGen::Mongo::Driver::ObjectID.new row['_id'] ||= Mongo::ObjectID.new
row row
end end
end end
@ -205,7 +205,7 @@ completely harmless; strict mode is a programmer convenience only.
To turn on strict mode, either pass in :strict => true when obtaining a DB To turn on strict mode, either pass in :strict => true when obtaining a DB
object or call the :strict= method: object or call the :strict= method:
db = XGen::Mongo::Driver::Mongo.new.db('dbname', :strict => true) db = Mongo::Mongo.new.db('dbname', :strict => true)
# I'm feeling lax # I'm feeling lax
db.strict = false db.strict = false
# No, I'm not! # No, I'm not!

View File

@ -3,7 +3,7 @@
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
TRIALS = 100000 TRIALS = 100000

View File

@ -7,14 +7,14 @@ require 'irb'
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = org_argv[0] || ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = org_argv[0] || ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = org_argv[1] || ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = org_argv[1] || ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
dbnm = org_argv[2] || ENV['MONGO_RUBY_DRIVER_DB'] || 'ruby-mongo-console' dbnm = org_argv[2] || ENV['MONGO_RUBY_DRIVER_DB'] || 'ruby-mongo-console'
puts "Connecting to #{host}:#{port} (CONN) on with database #{dbnm} (DB)" puts "Connecting to #{host}:#{port} (CONN) on with database #{dbnm} (DB)"
CONN = Mongo.new(host, port) CONN = Mongo::Mongo.new(host, port)
DB = CONN.db(dbnm) DB = CONN.db(dbnm)
puts "Starting IRB session..." puts "Starting IRB session..."

View File

@ -5,7 +5,7 @@
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
TRIALS = 2 TRIALS = 2
PER_TRIAL = 5000 PER_TRIAL = 5000
@ -50,9 +50,9 @@ def benchmark(str, proc, n, db, coll_name, object, setup=nil)
end end
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
connection = Mongo.new(host, port) connection = Mongo::Mongo.new(host, port)
connection.drop_database("benchmark") connection.drop_database("benchmark")
db = connection.db('benchmark') db = connection.db('benchmark')

View File

@ -2,13 +2,13 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
require 'pp' require 'pp'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.create_collection('test') coll = db.create_collection('test')
# Erase all records from collection, if any # Erase all records from collection, if any

View File

@ -4,10 +4,10 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = XGen::Mongo::Driver::Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.collection('test') coll = db.collection('test')
coll.clear coll.clear

View File

@ -9,13 +9,13 @@ end
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts ">> Connecting to #{host}:#{port}" puts ">> Connecting to #{host}:#{port}"
DB = Mongo.new(host, port).db('ruby-mongo-blog') DB = Mongo::Mongo.new(host, port).db('ruby-mongo-blog')
LINE_SIZE = 120 LINE_SIZE = 120
puts "=" * LINE_SIZE puts "=" * LINE_SIZE

View File

@ -1,13 +1,13 @@
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
db.drop_collection('test') db.drop_collection('test')
# A capped collection has a max size and optionally a max number of records. # A capped collection has a max size and optionally a max number of records.

View File

@ -2,13 +2,13 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
require 'pp' require 'pp'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.collection('test') coll = db.collection('test')
# Erase all records from collection, if any # Erase all records from collection, if any

View File

@ -2,14 +2,14 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
require 'mongo/gridfs' require 'mongo/gridfs'
include XGen::Mongo::Driver include Mongo
include XGen::Mongo::GridFS include GridFS
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
def dump(db, fname) def dump(db, fname)
GridStore.open(db, fname, 'r') { |f| puts f.read } GridStore.open(db, fname, 'r') { |f| puts f.read }

View File

@ -7,13 +7,13 @@ end
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts ">> Connecting to #{host}:#{port}" puts ">> Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-index_test') db = Mongo::Mongo.new(host, port).db('ruby-mongo-index_test')
puts ">> Dropping collection test" puts ">> Dropping collection test"
begin begin
@ -76,7 +76,7 @@ end
puts ">> Dropping index" puts ">> Dropping index"
begin begin
res = coll.drop_index "all" res = coll.drop_index "all_1"
puts "dropped : #{res.inspect}" puts "dropped : #{res.inspect}"
rescue => e rescue => e
puts "Error: #{e.errmsg}" puts "Error: #{e.errmsg}"

View File

@ -1,13 +1,13 @@
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.collection('test') coll = db.collection('test')
# Erase all records from collection, if any # Erase all records from collection, if any

View File

@ -2,13 +2,13 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
require 'pp' require 'pp'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.collection('test') coll = db.collection('test')
# Remove all records, if any # Remove all records, if any

View File

@ -1,13 +1,13 @@
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.collection('test') coll = db.collection('test')
# Erase all records from collection, if any # Erase all records from collection, if any

View File

@ -1,13 +1,13 @@
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
db.drop_collection('does-not-exist') db.drop_collection('does-not-exist')
db.create_collection('test') db.create_collection('test')

View File

@ -2,13 +2,13 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo' require 'mongo'
require 'pp' require 'pp'
include XGen::Mongo::Driver include Mongo
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || XGen::Mongo::Driver::Mongo::DEFAULT_PORT port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Mongo::DEFAULT_PORT
puts "Connecting to #{host}:#{port}" puts "Connecting to #{host}:#{port}"
db = Mongo.new(host, port).db('ruby-mongo-examples') db = Mongo::Mongo.new(host, port).db('ruby-mongo-examples')
coll = db.collection('test') coll = db.collection('test')
# Remove all records, if any # Remove all records, if any
@ -25,13 +25,8 @@ coll.insert('array' => [1, 2, 3],
'float' => 33.33333, 'float' => 33.33333,
'regex' => /foobar/i, 'regex' => /foobar/i,
'boolean' => true, 'boolean' => true,
'$where' => Code.new('this.x == 3'), 'where' => Code.new('this.x == 3'),
'dbref' => DBRef.new(coll.name, ObjectID.new), 'dbref' => DBRef.new(coll.name, ObjectID.new),
# NOTE: the undefined type is not saved to the database properly. This is a
# Mongo bug. However, the undefined type may go away completely.
# 'undef' => Undefined.new,
'null' => nil, 'null' => nil,
'symbol' => :zildjian) 'symbol' => :zildjian)

View File

@ -275,7 +275,7 @@ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow
case T_STRING: case T_STRING:
{ {
if (strcmp(rb_class2name(RBASIC(value)->klass), if (strcmp(rb_class2name(RBASIC(value)->klass),
"XGen::Mongo::Driver::Code") == 0) { "Mongo::Code") == 0) {
int start_position, length_location, length, total_length; int start_position, length_location, length, total_length;
write_name_and_type(buffer, key, 0x0F); write_name_and_type(buffer, key, 0x0F);
@ -314,7 +314,7 @@ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow
{ {
// TODO there has to be a better way to do these checks... // TODO there has to be a better way to do these checks...
const char* cls = rb_class2name(RBASIC(value)->klass); const char* cls = rb_class2name(RBASIC(value)->klass);
if (strcmp(cls, "XGen::Mongo::Driver::Binary") == 0 || if (strcmp(cls, "Mongo::Binary") == 0 ||
strcmp(cls, "ByteBuffer") == 0) { strcmp(cls, "ByteBuffer") == 0) {
const char subtype = strcmp(cls, "ByteBuffer") ? const char subtype = strcmp(cls, "ByteBuffer") ?
(const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2; (const char)FIX2INT(rb_funcall(value, rb_intern("subtype"), 0)) : 2;
@ -333,7 +333,7 @@ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow
buffer_write_bytes(buffer, RSTRING_PTR(string_data), length); buffer_write_bytes(buffer, RSTRING_PTR(string_data), length);
break; break;
} }
if (strcmp(cls, "XGen::Mongo::Driver::ObjectID") == 0) { if (strcmp(cls, "Mongo::ObjectID") == 0) {
VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0); VALUE as_array = rb_funcall(value, rb_intern("to_a"), 0);
int i; int i;
write_name_and_type(buffer, key, 0x07); write_name_and_type(buffer, key, 0x07);
@ -343,7 +343,7 @@ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow
} }
break; break;
} }
if (strcmp(cls, "XGen::Mongo::Driver::DBRef") == 0) { if (strcmp(cls, "Mongo::DBRef") == 0) {
int start_position, length_location, obj_length; int start_position, length_location, obj_length;
VALUE ns, oid; VALUE ns, oid;
write_name_and_type(buffer, key, 0x03); write_name_and_type(buffer, key, 0x03);
@ -364,7 +364,7 @@ static int write_element_allow_id(VALUE key, VALUE value, VALUE extra, int allow
memcpy(buffer->buffer + length_location, &obj_length, 4); memcpy(buffer->buffer + length_location, &obj_length, 4);
break; break;
} }
if (strcmp(cls, "XGen::Mongo::Driver::Undefined") == 0) { if (strcmp(cls, "Mongo::Undefined") == 0) {
write_name_and_type(buffer, key, 0x0A); // just use nil type write_name_and_type(buffer, key, 0x0A); // just use nil type
break; break;
} }
@ -736,25 +736,22 @@ static VALUE method_deserialize(VALUE self, VALUE bson) {
} }
void Init_cbson() { void Init_cbson() {
VALUE driver, CBson; VALUE mongo, CBson;
Time = rb_const_get(rb_cObject, rb_intern("Time")); Time = rb_const_get(rb_cObject, rb_intern("Time"));
driver = rb_const_get(rb_const_get(rb_const_get(rb_cObject, mongo = rb_const_get(rb_cObject, rb_intern("Mongo"));
rb_intern("XGen")),
rb_intern("Mongo")),
rb_intern("Driver"));
rb_require("mongo/types/binary"); rb_require("mongo/types/binary");
Binary = rb_const_get(driver, rb_intern("Binary")); Binary = rb_const_get(mongo, rb_intern("Binary"));
rb_require("mongo/types/objectid"); rb_require("mongo/types/objectid");
ObjectID = rb_const_get(driver, rb_intern("ObjectID")); ObjectID = rb_const_get(mongo, rb_intern("ObjectID"));
rb_require("mongo/types/dbref"); rb_require("mongo/types/dbref");
DBRef = rb_const_get(driver, rb_intern("DBRef")); DBRef = rb_const_get(mongo, rb_intern("DBRef"));
rb_require("mongo/types/code"); rb_require("mongo/types/code");
Code = rb_const_get(driver, rb_intern("Code")); Code = rb_const_get(mongo, rb_intern("Code"));
rb_require("mongo/types/regexp_of_holding"); rb_require("mongo/types/regexp_of_holding");
RegexpOfHolding = rb_const_get(driver, rb_intern("RegexpOfHolding")); RegexpOfHolding = rb_const_get(mongo, rb_intern("RegexpOfHolding"));
rb_require("mongo/errors"); rb_require("mongo/errors");
InvalidName = rb_const_get(driver, rb_intern("InvalidName")); InvalidName = rb_const_get(mongo, rb_intern("InvalidName"));
rb_require("mongo/util/ordered_hash"); rb_require("mongo/util/ordered_hash");
OrderedHash = rb_const_get(rb_cObject, rb_intern("OrderedHash")); OrderedHash = rb_const_get(rb_cObject, rb_intern("OrderedHash"));

View File

@ -12,9 +12,7 @@ require 'mongo/cursor'
require 'mongo/collection' require 'mongo/collection'
require 'mongo/admin' require 'mongo/admin'
module XGen module Mongo
module Mongo ASCENDING = 1
ASCENDING = 1 DESCENDING = -1
DESCENDING = -1
end
end end

View File

@ -16,72 +16,68 @@
require 'mongo/util/ordered_hash' require 'mongo/util/ordered_hash'
module XGen module Mongo
module Mongo
module Driver
# Provide administrative database methods: those having to do with # Provide administrative database methods: those having to do with
# profiling and validation. # profiling and validation.
class Admin class Admin
def initialize(db) def initialize(db)
@db = db @db = db
end end
# Return the current database profiling level.
def profiling_level
oh = OrderedHash.new
oh[:profile] = -1
doc = @db.db_command(oh)
raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc) && doc['was'].kind_of?(Numeric)
case doc['was'].to_i
when 0
:off
when 1
:slow_only
when 2
:all
else
raise "Error: illegal profiling level value #{doc['was']}"
end
end
# Set database profiling level to :off, :slow_only, or :all.
def profiling_level=(level)
oh = OrderedHash.new
oh[:profile] = case level
when :off
0
when :slow_only
1
when :all
2
else
raise "Error: illegal profiling level value #{level}"
end
doc = @db.db_command(oh)
raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc)
end
# Return an array contining current profiling information from the
# database.
def profiling_info
@db.query(Collection.new(@db, DB::SYSTEM_PROFILE_COLLECTION), Query.new({})).to_a
end
# Validate a named collection by raising an exception if there is a
# problem or returning an interesting hash (see especially the
# 'result' string value) if all is well.
def validate_collection(name)
doc = @db.db_command(:validate => name)
raise "Error with validate command: #{doc.inspect}" unless @db.ok?(doc)
result = doc['result']
raise "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
raise "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
doc
end
# Return the current database profiling level.
def profiling_level
oh = OrderedHash.new
oh[:profile] = -1
doc = @db.db_command(oh)
raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc) && doc['was'].kind_of?(Numeric)
case doc['was'].to_i
when 0
:off
when 1
:slow_only
when 2
:all
else
raise "Error: illegal profiling level value #{doc['was']}"
end end
end end
# Set database profiling level to :off, :slow_only, or :all.
def profiling_level=(level)
oh = OrderedHash.new
oh[:profile] = case level
when :off
0
when :slow_only
1
when :all
2
else
raise "Error: illegal profiling level value #{level}"
end
doc = @db.db_command(oh)
raise "Error with profile command: #{doc.inspect}" unless @db.ok?(doc)
end
# Return an array contining current profiling information from the
# database.
def profiling_info
@db.query(Collection.new(@db, DB::SYSTEM_PROFILE_COLLECTION), Query.new({})).to_a
end
# Validate a named collection by raising an exception if there is a
# problem or returning an interesting hash (see especially the
# 'result' string value) if all is well.
def validate_collection(name)
doc = @db.db_command(:validate => name)
raise "Error with validate command: #{doc.inspect}" unless @db.ok?(doc)
result = doc['result']
raise "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
raise "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
doc
end
end end
end end

View File

@ -16,320 +16,318 @@
require 'mongo/query' require 'mongo/query'
module XGen module Mongo
module Mongo
module Driver
# A named collection of records in a database. # A named collection of records in a database.
class Collection class Collection
attr_reader :db, :name, :hint attr_reader :db, :name, :hint
def initialize(db, name) def initialize(db, name)
case name case name
when Symbol, String when Symbol, String
else else
raise TypeError, "new_name must be a string or symbol" raise TypeError, "new_name must be a string or symbol"
end end
name = name.to_s name = name.to_s
if name.empty? or name.include? ".." if name.empty? or name.include? ".."
raise InvalidName, "collection names cannot be empty" raise InvalidName, "collection names cannot be empty"
end end
if name.include? "$" and not name.match(/^\$cmd/) if name.include? "$" and not name.match(/^\$cmd/)
raise InvalidName, "collection names must not contain '$'" raise InvalidName, "collection names must not contain '$'"
end end
if name.match(/^\./) or name.match(/\.$/) if name.match(/^\./) or name.match(/\.$/)
raise InvalidName, "collection names must not start or end with '.'" raise InvalidName, "collection names must not start or end with '.'"
end end
@db, @name = db, name @db, @name = db, name
@hint = nil @hint = nil
end
# Get a sub-collection of this collection by name.
#
# Raises InvalidName if an invalid collection name is used.
#
# :name :: the name of the collection to get
def [](name)
name = "#{self.name}.#{name}"
return Collection.new(self, name) if !db.strict? || db.collection_names.include?(name)
raise "Collection #{name} doesn't exist. Currently in strict mode."
end
# Set hint fields to use and return +self+. hint may be a single field
# name, array of field names, or a hash (preferably an OrderedHash).
# May be +nil+.
def hint=(hint)
@hint = normalize_hint_fields(hint)
self
end
# Query the database.
#
# The +selector+ argument is a prototype document that all results must
# match. For example:
#
# collection.find({"hello" => "world"})
#
# only matches documents that have a key "hello" with value "world".
# Matches can have other keys *in addition* to "hello".
#
# If given an optional block +find+ will yield a Cursor to that block,
# close the cursor, and then return nil. This guarantees that partially
# evaluated cursors will be closed. If given no block +find+ returns a
# cursor.
#
# :selector :: A document (hash) specifying elements which must be
# present for a document to be included in the result set.
#
# Options:
# :fields :: Array of field names that should be returned in the result
# set ("_id" will always be included). By limiting results
# to a certain subset of fields you can cut down on network
# traffic and decoding time.
# :offset :: Start at this record when returning records
# :limit :: Maximum number of records to return
# :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
# ascending, -1 == descending, or array of field names (all
# assumed to be sorted in ascending order).
# :hint :: See #hint. This option overrides the collection-wide value.
# :snapshot :: If true, snapshot mode will be used for this query.
# Snapshot mode assures no duplicates are returned, or
# objects missed, which were preset at both the start and
# end of the query's execution. For details see
# http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
def find(selector={}, options={})
fields = options.delete(:fields)
fields = ["_id"] if fields && fields.empty?
offset = options.delete(:offset) || 0
limit = options.delete(:limit) || 0
sort = options.delete(:sort)
hint = options.delete(:hint)
snapshot = options.delete(:snapshot)
if hint
hint = normalize_hint_fields(hint)
else
hint = @hint # assumed to be normalized already
end
raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
cursor = @db.query(self, Query.new(selector, fields, offset, limit, sort, hint, snapshot))
if block_given?
yield cursor
cursor.close()
nil
else
cursor
end
end
# Get a single object from the database.
#
# Raises TypeError if the argument is of an improper type. Returns a
# single document (hash), or nil if no result is found.
#
# :spec_or_object_id :: a hash specifying elements which must be
# present for a document to be included in the result set OR an
# instance of ObjectID to be used as the value for an _id query.
# if nil an empty spec, {}, will be used.
# :options :: options, as passed to Collection#find
def find_one(spec_or_object_id=nil, options={})
spec = case spec_or_object_id
when nil
{}
when ObjectID
{:_id => spec_or_object_id}
when Hash
spec_or_object_id
else
raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
end
find(spec, options.merge(:limit => -1)).next_object
end
# DEPRECATED - use find_one instead
#
# Find the first record that matches +selector+. See #find.
def find_first(selector={}, options={})
warn "Collection#find_first is deprecated and will be removed. Please use Collection#find_one instead."
find_one(selector, options)
end
# Save a document in this collection.
#
# If +to_save+ already has an '_id' then an update (upsert) operation
# is performed and any existing document with that _id is overwritten.
# Otherwise an insert operation is performed. Returns the _id of the
# saved document.
#
# :to_save :: the document (a hash) to be saved
#
# Options:
# :safe :: if true, check that the save succeeded. OperationFailure
# will be raised on an error. Checking for safety requires an extra
# round-trip to the database
def save(to_save, options={})
if id = to_save[:_id] || to_save['_id']
update({:_id => id}, to_save, :upsert => true, :safe => options.delete(:safe))
id
else
insert(to_save, :safe => options.delete(:safe))
end
end
# Insert a document(s) into this collection.
#
# "<<" is aliased to this method. Returns the _id of the inserted
# document or a list of _ids of the inserted documents. The object(s)
# may have been modified by the database's PK factory, if it has one.
#
# :doc_or_docs :: a document (as a hash) or Array of documents to be
# inserted
#
# Options:
# :safe :: if true, check that the insert succeeded. OperationFailure
# will be raised on an error. Checking for safety requires an extra
# round-trip to the database
def insert(doc_or_docs, options={})
doc_or_docs = [doc_or_docs] if !doc_or_docs.is_a?(Array)
res = @db.insert_into_db(@name, doc_or_docs)
if options.delete(:safe)
error = @db.error
if error
raise OperationFailure, error
end end
end
res.size > 1 ? res : res.first
end
alias_method :<<, :insert
# Get a sub-collection of this collection by name. # Remove the records that match +selector+.
# def remove(selector={})
# Raises InvalidName if an invalid collection name is used. @db.remove_from_db(@name, selector)
# end
# :name :: the name of the collection to get
def [](name) # Remove all records.
name = "#{self.name}.#{name}" def clear
return Collection.new(self, name) if !db.strict? || db.collection_names.include?(name) remove({})
raise "Collection #{name} doesn't exist. Currently in strict mode." end
# DEPRECATED - use update(... :upsert => true) instead
#
# Update records that match +selector+ by applying +obj+ as an update.
# If no match, inserts (???).
def repsert(selector, obj)
warn "Collection#repsert is deprecated and will be removed. Please use Collection#update instead."
update(selector, obj, :upsert => true)
end
# DEPRECATED - use update(... :upsert => false) instead
#
# Update records that match +selector+ by applying +obj+ as an update.
def replace(selector, obj)
warn "Collection#replace is deprecated and will be removed. Please use Collection#update instead."
update(selector, obj)
end
# DEPRECATED - use update(... :upsert => false) instead
#
# Update records that match +selector+ by applying +obj+ as an update.
# Both +selector+ and +modifier_obj+ are required.
def modify(selector, modifier_obj)
warn "Collection#modify is deprecated and will be removed. Please use Collection#update instead."
update(selector, modifier_obj)
end
# Update a document(s) in this collection.
#
# :spec :: a hash specifying elements which must be present for
# a document to be updated
# :document :: a hash specifying the fields to be changed in the
# selected document(s), or (in the case of an upsert) the document to
# be inserted
#
# Options:
# :upsert :: if true, perform an upsert operation
# :safe :: if true, check that the update succeeded. OperationFailure
# will be raised on an error. Checking for safety requires an extra
# round-trip to the database
def update(spec, document, options={})
upsert = options.delete(:upsert)
safe = options.delete(:safe)
if upsert
@db.repsert_in_db(@name, spec, document)
else
@db.replace_in_db(@name, spec, document)
end
if safe
error = @db.error
if error
raise OperationFailure, error
end end
end
end
# Set hint fields to use and return +self+. hint may be a single field # Create a new index. +field_or_spec+
# name, array of field names, or a hash (preferably an OrderedHash). # should be either a single field name or a Array of [field name,
# May be +nil+. # direction] pairs. Directions should be specified as
def hint=(hint) # Mongo::ASCENDING or Mongo::DESCENDING.
@hint = normalize_hint_fields(hint) # +unique+ is an optional boolean indicating whether this index
self # should enforce a uniqueness constraint.
def create_index(field_or_spec, unique=false)
@db.create_index(@name, field_or_spec, unique)
end
# Drop index +name+.
def drop_index(name)
@db.drop_index(@name, name)
end
# Drop all indexes.
def drop_indexes
# just need to call drop indexes with no args; will drop them all
@db.drop_index(@name, '*')
end
# Drop the entire collection. USE WITH CAUTION.
def drop
@db.drop_collection(@name)
end
# Perform a query similar to an SQL group by operation.
#
# Returns an array of grouped items.
#
# :keys :: Array of fields to group by
# :condition :: specification of rows to be considered (as a 'find'
# query specification)
# :initial :: initial value of the aggregation counter object
# :reduce :: aggregation function as a JavaScript string
# :command :: if true, run the group as a command instead of in an
# eval - it is likely that this option will eventually be
# deprecated and all groups will be run as commands
def group(keys, condition, initial, reduce, command=false)
if command
hash = {}
keys.each do |k|
hash[k] = 1
end end
result = @db.db_command({"group" =>
# Query the database. {
# "ns" => @name,
# The +selector+ argument is a prototype document that all results must "$reduce" => Code.new(reduce),
# match. For example: "key" => hash,
# "cond" => condition,
# collection.find({"hello" => "world"}) "initial" => initial}})
# if result["ok"] == 1
# only matches documents that have a key "hello" with value "world". return result["retval"]
# Matches can have other keys *in addition* to "hello". else
# raise OperationFailure, "group command failed: #{result['errmsg']}"
# If given an optional block +find+ will yield a Cursor to that block,
# close the cursor, and then return nil. This guarantees that partially
# evaluated cursors will be closed. If given no block +find+ returns a
# cursor.
#
# :selector :: A document (hash) specifying elements which must be
# present for a document to be included in the result set.
#
# Options:
# :fields :: Array of field names that should be returned in the result
# set ("_id" will always be included). By limiting results
# to a certain subset of fields you can cut down on network
# traffic and decoding time.
# :offset :: Start at this record when returning records
# :limit :: Maximum number of records to return
# :sort :: Either hash of field names as keys and 1/-1 as values; 1 ==
# ascending, -1 == descending, or array of field names (all
# assumed to be sorted in ascending order).
# :hint :: See #hint. This option overrides the collection-wide value.
# :snapshot :: If true, snapshot mode will be used for this query.
# Snapshot mode assures no duplicates are returned, or
# objects missed, which were preset at both the start and
# end of the query's execution. For details see
# http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
def find(selector={}, options={})
fields = options.delete(:fields)
fields = ["_id"] if fields && fields.empty?
offset = options.delete(:offset) || 0
limit = options.delete(:limit) || 0
sort = options.delete(:sort)
hint = options.delete(:hint)
snapshot = options.delete(:snapshot)
if hint
hint = normalize_hint_fields(hint)
else
hint = @hint # assumed to be normalized already
end
raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
cursor = @db.query(self, Query.new(selector, fields, offset, limit, sort, hint, snapshot))
if block_given?
yield cursor
cursor.close()
nil
else
cursor
end
end end
end
# Get a single object from the database. group_function = <<EOS
#
# Raises TypeError if the argument is of an improper type. Returns a
# single document (hash), or nil if no result is found.
#
# :spec_or_object_id :: a hash specifying elements which must be
# present for a document to be included in the result set OR an
# instance of ObjectID to be used as the value for an _id query.
# if nil an empty spec, {}, will be used.
# :options :: options, as passed to Collection#find
def find_one(spec_or_object_id=nil, options={})
spec = case spec_or_object_id
when nil
{}
when ObjectID
{:_id => spec_or_object_id}
when Hash
spec_or_object_id
else
raise TypeError, "spec_or_object_id must be an instance of ObjectID or Hash, or nil"
end
find(spec, options.merge(:limit => -1)).next_object
end
# DEPRECATED - use find_one instead
#
# Find the first record that matches +selector+. See #find.
def find_first(selector={}, options={})
warn "Collection#find_first is deprecated and will be removed. Please use Collection#find_one instead."
find_one(selector, options)
end
# Save a document in this collection.
#
# If +to_save+ already has an '_id' then an update (upsert) operation
# is performed and any existing document with that _id is overwritten.
# Otherwise an insert operation is performed. Returns the _id of the
# saved document.
#
# :to_save :: the document (a hash) to be saved
#
# Options:
# :safe :: if true, check that the save succeeded. OperationFailure
# will be raised on an error. Checking for safety requires an extra
# round-trip to the database
def save(to_save, options={})
if id = to_save[:_id] || to_save['_id']
update({:_id => id}, to_save, :upsert => true, :safe => options.delete(:safe))
id
else
insert(to_save, :safe => options.delete(:safe))
end
end
# Insert a document(s) into this collection.
#
# "<<" is aliased to this method. Returns the _id of the inserted
# document or a list of _ids of the inserted documents. The object(s)
# may have been modified by the database's PK factory, if it has one.
#
# :doc_or_docs :: a document (as a hash) or Array of documents to be
# inserted
#
# Options:
# :safe :: if true, check that the insert succeeded. OperationFailure
# will be raised on an error. Checking for safety requires an extra
# round-trip to the database
def insert(doc_or_docs, options={})
doc_or_docs = [doc_or_docs] if !doc_or_docs.is_a?(Array)
res = @db.insert_into_db(@name, doc_or_docs)
if options.delete(:safe)
error = @db.error
if error
raise OperationFailure, error
end
end
res.size > 1 ? res : res.first
end
alias_method :<<, :insert
# Remove the records that match +selector+.
def remove(selector={})
@db.remove_from_db(@name, selector)
end
# Remove all records.
def clear
remove({})
end
# DEPRECATED - use update(... :upsert => true) instead
#
# Update records that match +selector+ by applying +obj+ as an update.
# If no match, inserts (???).
def repsert(selector, obj)
warn "Collection#repsert is deprecated and will be removed. Please use Collection#update instead."
update(selector, obj, :upsert => true)
end
# DEPRECATED - use update(... :upsert => false) instead
#
# Update records that match +selector+ by applying +obj+ as an update.
def replace(selector, obj)
warn "Collection#replace is deprecated and will be removed. Please use Collection#update instead."
update(selector, obj)
end
# DEPRECATED - use update(... :upsert => false) instead
#
# Update records that match +selector+ by applying +obj+ as an update.
# Both +selector+ and +modifier_obj+ are required.
def modify(selector, modifier_obj)
warn "Collection#modify is deprecated and will be removed. Please use Collection#update instead."
update(selector, modifier_obj)
end
# Update a document(s) in this collection.
#
# :spec :: a hash specifying elements which must be present for
# a document to be updated
# :document :: a hash specifying the fields to be changed in the
# selected document(s), or (in the case of an upsert) the document to
# be inserted
#
# Options:
# :upsert :: if true, perform an upsert operation
# :safe :: if true, check that the update succeeded. OperationFailure
# will be raised on an error. Checking for safety requires an extra
# round-trip to the database
def update(spec, document, options={})
upsert = options.delete(:upsert)
safe = options.delete(:safe)
if upsert
@db.repsert_in_db(@name, spec, document)
else
@db.replace_in_db(@name, spec, document)
end
if safe
error = @db.error
if error
raise OperationFailure, error
end
end
end
# Create a new index. +field_or_spec+
# should be either a single field name or a Array of [field name,
# direction] pairs. Directions should be specified as
# XGen::Mongo::ASCENDING or XGen::Mongo::DESCENDING.
# +unique+ is an optional boolean indicating whether this index
# should enforce a uniqueness constraint.
def create_index(field_or_spec, unique=false)
@db.create_index(@name, field_or_spec, unique)
end
# Drop index +name+.
def drop_index(name)
@db.drop_index(@name, name)
end
# Drop all indexes.
def drop_indexes
# just need to call drop indexes with no args; will drop them all
@db.drop_index(@name, '*')
end
# Drop the entire collection. USE WITH CAUTION.
def drop
@db.drop_collection(@name)
end
# Perform a query similar to an SQL group by operation.
#
# Returns an array of grouped items.
#
# :keys :: Array of fields to group by
# :condition :: specification of rows to be considered (as a 'find'
# query specification)
# :initial :: initial value of the aggregation counter object
# :reduce :: aggregation function as a JavaScript string
# :command :: if true, run the group as a command instead of in an
# eval - it is likely that this option will eventually be
# deprecated and all groups will be run as commands
def group(keys, condition, initial, reduce, command=false)
if command
hash = {}
keys.each do |k|
hash[k] = 1
end
result = @db.db_command({"group" =>
{
"ns" => @name,
"$reduce" => Code.new(reduce),
"key" => hash,
"cond" => condition,
"initial" => initial}})
if result["ok"] == 1
return result["retval"]
else
raise OperationFailure, "group command failed: #{result['errmsg']}"
end
end
group_function = <<EOS
function () { function () {
var c = db[ns].find(condition); var c = db[ns].find(condition);
var map = new Map(); var map = new Map();
@ -354,88 +352,85 @@ function () {
return {"result": map.values()}; return {"result": map.values()};
} }
EOS EOS
return @db.eval(Code.new(group_function, return @db.eval(Code.new(group_function,
{ {
"ns" => @name, "ns" => @name,
"keys" => keys, "keys" => keys,
"condition" => condition, "condition" => condition,
"initial" => initial "initial" => initial
}))["result"] }))["result"]
end end
# Rename this collection. # Rename this collection.
# #
# If operating in auth mode, client must be authorized as an admin to # If operating in auth mode, client must be authorized as an admin to
# perform this operation. Raises +InvalidName+ if +new_name+ is an invalid # perform this operation. Raises +InvalidName+ if +new_name+ is an invalid
# collection name. # collection name.
# #
# :new_name :: new name for this collection # :new_name :: new name for this collection
def rename(new_name) def rename(new_name)
case new_name case new_name
when Symbol, String when Symbol, String
else else
raise TypeError, "new_name must be a string or symbol" raise TypeError, "new_name must be a string or symbol"
end end
new_name = new_name.to_s new_name = new_name.to_s
if new_name.empty? or new_name.include? ".." if new_name.empty? or new_name.include? ".."
raise InvalidName, "collection names cannot be empty" raise InvalidName, "collection names cannot be empty"
end end
if new_name.include? "$" if new_name.include? "$"
raise InvalidName, "collection names must not contain '$'" raise InvalidName, "collection names must not contain '$'"
end end
if new_name.match(/^\./) or new_name.match(/\.$/) if new_name.match(/^\./) or new_name.match(/\.$/)
raise InvalidName, "collection names must not start or end with '.'" raise InvalidName, "collection names must not start or end with '.'"
end end
@db.rename_collection(@name, new_name) @db.rename_collection(@name, new_name)
end end
# Get information on the indexes for the collection +collection_name+. # Get information on the indexes for the collection +collection_name+.
# Returns a hash where the keys are index names (as returned by # Returns a hash where the keys are index names (as returned by
# Collection#create_index and the values are lists of [key, direction] # Collection#create_index and the values are lists of [key, direction]
# pairs specifying the index (as passed to Collection#create_index). # pairs specifying the index (as passed to Collection#create_index).
def index_information def index_information
@db.index_information(@name) @db.index_information(@name)
end end
# Return a hash containing options that apply to this collection. # Return a hash containing options that apply to this collection.
# 'create' will be the collection name. For the other possible keys # 'create' will be the collection name. For the other possible keys
# and values, see DB#create_collection. # and values, see DB#create_collection.
def options def options
@db.collections_info(@name).next_object()['options'] @db.collections_info(@name).next_object()['options']
end end
# Get the number of documents in this collection. # Get the number of documents in this collection.
# #
# Specifying a +selector+ is DEPRECATED and will be removed. Please use # Specifying a +selector+ is DEPRECATED and will be removed. Please use
# find(selector).count() instead. # find(selector).count() instead.
def count(selector=nil) def count(selector=nil)
if selector if selector
warn "specifying a selector for Collection#count is deprecated and will be removed. Please use Collection.find(selector).count instead." warn "specifying a selector for Collection#count is deprecated and will be removed. Please use Collection.find(selector).count instead."
end end
find(selector || {}).count() find(selector || {}).count()
end end
protected protected
def normalize_hint_fields(hint) def normalize_hint_fields(hint)
case hint case hint
when String when String
{hint => 1} {hint => 1}
when Hash when Hash
hint hint
when nil when nil
nil nil
else else
h = OrderedHash.new h = OrderedHash.new
hint.to_a.each { |k| h[k] = 1 } hint.to_a.each { |k| h[k] = 1 }
h h
end
end
end end
end end
end end
end end

View File

@ -18,232 +18,228 @@ require 'mongo/message'
require 'mongo/util/byte_buffer' require 'mongo/util/byte_buffer'
require 'mongo/util/bson' require 'mongo/util/bson'
module XGen module Mongo
module Mongo
module Driver
# A cursor over query results. Returned objects are hashes. # A cursor over query results. Returned objects are hashes.
class Cursor class Cursor
include Enumerable include Enumerable
RESPONSE_HEADER_SIZE = 20 RESPONSE_HEADER_SIZE = 20
attr_reader :db, :collection, :query attr_reader :db, :collection, :query
def initialize(db, collection, query, admin=false) def initialize(db, collection, query, admin=false)
@db, @collection, @query, @admin = db, collection, query, admin @db, @collection, @query, @admin = db, collection, query, admin
@num_to_return = @query.number_to_return || 0 @num_to_return = @query.number_to_return || 0
@cache = [] @cache = []
@closed = false @closed = false
@can_call_to_a = true @can_call_to_a = true
@query_run = false @query_run = false
@rows = nil @rows = nil
end end
def closed?; @closed; end def closed?; @closed; end
# Internal method, not for general use. Return +true+ if there are # Internal method, not for general use. Return +true+ if there are
# more records to retrieve. We do not check @num_to_return; #each is # more records to retrieve. We do not check @num_to_return; #each is
# responsible for doing that. # responsible for doing that.
def more? def more?
num_remaining > 0 num_remaining > 0
end end
# Return the next object or nil if there are no more. Raises an error # Return the next object or nil if there are no more. Raises an error
# if necessary. # if necessary.
def next_object def next_object
refill_via_get_more if num_remaining == 0 refill_via_get_more if num_remaining == 0
o = @cache.shift o = @cache.shift
if o && o['$err'] if o && o['$err']
err = o['$err'] err = o['$err']
# If the server has stopped being the master (e.g., it's one of a # If the server has stopped being the master (e.g., it's one of a
# pair but it has died or something like that) then we close that # pair but it has died or something like that) then we close that
# connection. If the db has auto connect option and a pair of # connection. If the db has auto connect option and a pair of
# servers, next request will re-open on master server. # servers, next request will re-open on master server.
@db.close if err == "not master" @db.close if err == "not master"
raise err raise err
end
o
end
# Get the size of the results set for this query.
#
# Returns the number of objects in the results set for this query. Does
# not take limit and skip into account. Raises OperationFailure on a
# database error.
def count
command = OrderedHash["count", @collection.name,
"query", @query.selector]
response = @db.db_command(command)
return response['n'].to_i if response['ok'] == 1
return 0 if response['errmsg'] == "ns missing"
raise OperationFailure, "Count failed: #{response['errmsg']}"
end
# Iterate over each object, yielding it to the given block. At most
# @num_to_return records are returned (or all of them, if
# @num_to_return is 0).
#
# If #to_a has already been called then this method uses the array
# that we store internally. In that case, #each can be called multiple
# times because it re-uses that array.
#
# You can call #each after calling #to_a (multiple times even, because
# it will use the internally-stored array), but you can't call #to_a
# after calling #each unless you also called it before calling #each.
# If you try to do that, an error will be raised.
def each
if @rows # Already turned into an array
@rows.each { |row| yield row }
else
num_returned = 0
while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
yield next_object()
num_returned += 1
end
@can_call_to_a = false
end
end
# Return all of the rows (up to the +num_to_return+ value specified in
# #new) as an array. Calling this multiple times will work fine; it
# always returns the same array.
#
# Don't use this if you're expecting large amounts of data, of course.
# All of the returned rows are kept in an array stored in this object
# so it can be reused.
#
# You can call #each after calling #to_a (multiple times even, because
# it will use the internally-stored array), but you can't call #to_a
# after calling #each unless you also called it before calling #each.
# If you try to do that, an error will be raised.
def to_a
return @rows if @rows
raise "can't call Cursor#to_a after calling Cursor#each" unless @can_call_to_a
@rows = []
num_returned = 0
while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
@rows << next_object()
num_returned += 1
end
@rows
end
# Returns an explain plan record.
def explain
old_val = @query.explain
@query.explain = true
c = Cursor.new(@db, @collection, @query)
explanation = c.next_object
c.close
@query.explain = old_val
explanation
end
# Close the cursor.
#
# Note: if a cursor is read until exhausted (read until OP_QUERY or
# OP_GETMORE returns zero for the cursor id), there is no need to
# close it by calling this method.
def close
@db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
@cache = []
@cursor_id = 0
@closed = true
end
protected
def read_all
read_message_header
read_response_header
read_objects_off_wire
end
def read_objects_off_wire
while doc = next_object_on_wire
@cache << doc
end
end
def read_message_header
MessageHeader.new.read_header(@db)
end
def read_response_header
header_buf = ByteBuffer.new
header_buf.put_array(@db.receive_full(RESPONSE_HEADER_SIZE).unpack("C*"))
raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
header_buf.rewind
@result_flags = header_buf.get_int
@cursor_id = header_buf.get_long
@starting_from = header_buf.get_int
@n_returned = header_buf.get_int
@n_remaining = @n_returned
end
def num_remaining
refill_via_get_more if @cache.length == 0
@cache.length
end
private
def next_object_on_wire
send_query_if_needed
# if @n_remaining is 0 but we have a non-zero cursor, there are more
# to fetch, so do a GetMore operation, but don't do it here - do it
# when someone pulls an object out of the cache and it's empty
return nil if @n_remaining == 0
object_from_stream
end
def refill_via_get_more
if send_query_if_needed or @cursor_id == 0
return
end
@db._synchronize {
@db.send_to_db(GetMoreMessage.new(@admin ? 'admin' : @db.name, @collection.name, @cursor_id))
read_all
}
end
def object_from_stream
buf = ByteBuffer.new
buf.put_array(@db.receive_full(4).unpack("C*"))
buf.rewind
size = buf.get_int
buf.put_array(@db.receive_full(size - 4).unpack("C*"), 4)
@n_remaining -= 1
buf.rewind
BSON.new.deserialize(buf)
end
def send_query_if_needed
# Run query first time we request an object from the wire
if @query_run
false
else
@db._synchronize {
@db.send_query_message(QueryMessage.new(@admin ? 'admin' : @db.name, @collection.name, @query))
@query_run = true
read_all
}
true
end
end
def to_s
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
end
end end
o
end
# Get the size of the results set for this query.
#
# Returns the number of objects in the results set for this query. Does
# not take limit and skip into account. Raises OperationFailure on a
# database error.
def count
command = OrderedHash["count", @collection.name,
"query", @query.selector]
response = @db.db_command(command)
return response['n'].to_i if response['ok'] == 1
return 0 if response['errmsg'] == "ns missing"
raise OperationFailure, "Count failed: #{response['errmsg']}"
end
# Iterate over each object, yielding it to the given block. At most
# @num_to_return records are returned (or all of them, if
# @num_to_return is 0).
#
# If #to_a has already been called then this method uses the array
# that we store internally. In that case, #each can be called multiple
# times because it re-uses that array.
#
# You can call #each after calling #to_a (multiple times even, because
# it will use the internally-stored array), but you can't call #to_a
# after calling #each unless you also called it before calling #each.
# If you try to do that, an error will be raised.
def each
if @rows # Already turned into an array
@rows.each { |row| yield row }
else
num_returned = 0
while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
yield next_object()
num_returned += 1
end
@can_call_to_a = false
end
end
# Return all of the rows (up to the +num_to_return+ value specified in
# #new) as an array. Calling this multiple times will work fine; it
# always returns the same array.
#
# Don't use this if you're expecting large amounts of data, of course.
# All of the returned rows are kept in an array stored in this object
# so it can be reused.
#
# You can call #each after calling #to_a (multiple times even, because
# it will use the internally-stored array), but you can't call #to_a
# after calling #each unless you also called it before calling #each.
# If you try to do that, an error will be raised.
def to_a
return @rows if @rows
raise "can't call Cursor#to_a after calling Cursor#each" unless @can_call_to_a
@rows = []
num_returned = 0
while more? && (@num_to_return <= 0 || num_returned < @num_to_return)
@rows << next_object()
num_returned += 1
end
@rows
end
# Returns an explain plan record.
def explain
old_val = @query.explain
@query.explain = true
c = Cursor.new(@db, @collection, @query)
explanation = c.next_object
c.close
@query.explain = old_val
explanation
end
# Close the cursor.
#
# Note: if a cursor is read until exhausted (read until OP_QUERY or
# OP_GETMORE returns zero for the cursor id), there is no need to
# close it by calling this method.
def close
@db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
@cache = []
@cursor_id = 0
@closed = true
end
protected
def read_all
read_message_header
read_response_header
read_objects_off_wire
end
def read_objects_off_wire
while doc = next_object_on_wire
@cache << doc
end
end
def read_message_header
MessageHeader.new.read_header(@db)
end
def read_response_header
header_buf = ByteBuffer.new
header_buf.put_array(@db.receive_full(RESPONSE_HEADER_SIZE).unpack("C*"))
raise "Short read for DB response header; expected #{RESPONSE_HEADER_SIZE} bytes, saw #{header_buf.length}" unless header_buf.length == RESPONSE_HEADER_SIZE
header_buf.rewind
@result_flags = header_buf.get_int
@cursor_id = header_buf.get_long
@starting_from = header_buf.get_int
@n_returned = header_buf.get_int
@n_remaining = @n_returned
end
def num_remaining
refill_via_get_more if @cache.length == 0
@cache.length
end
private
def next_object_on_wire
send_query_if_needed
# if @n_remaining is 0 but we have a non-zero cursor, there are more
# to fetch, so do a GetMore operation, but don't do it here - do it
# when someone pulls an object out of the cache and it's empty
return nil if @n_remaining == 0
object_from_stream
end
def refill_via_get_more
if send_query_if_needed or @cursor_id == 0
return
end
@db._synchronize {
@db.send_to_db(GetMoreMessage.new(@admin ? 'admin' : @db.name, @collection.name, @cursor_id))
read_all
}
end
def object_from_stream
buf = ByteBuffer.new
buf.put_array(@db.receive_full(4).unpack("C*"))
buf.rewind
size = buf.get_int
buf.put_array(@db.receive_full(size - 4).unpack("C*"), 4)
@n_remaining -= 1
buf.rewind
BSON.new.deserialize(buf)
end
def send_query_if_needed
# Run query first time we request an object from the wire
if @query_run
false
else
@db._synchronize {
@db.send_query_message(QueryMessage.new(@admin ? 'admin' : @db.name, @collection.name, @query))
@query_run = true
read_all
}
true
end
end
def to_s
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
end end
end end
end end

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,10 @@
# Exceptions raised by the MongoDB driver. # Exceptions raised by the MongoDB driver.
module XGen module Mongo
module Mongo # Raised when a database operation fails.
module Driver class OperationFailure < RuntimeError; end
# Raised when a database operation fails.
class OperationFailure < RuntimeError; end
# Raised when an invalid name is used. # Raised when an invalid name is used.
class InvalidName < RuntimeError; end class InvalidName < RuntimeError; end
end
end
end end

View File

@ -19,78 +19,74 @@ require 'mongo/util/byte_buffer'
require 'mongo/util/ordered_hash' require 'mongo/util/ordered_hash'
module XGen module GridFS
module Mongo
module GridFS
# A chunk stores a portion of GridStore data. # A chunk stores a portion of GridStore data.
class Chunk class Chunk
DEFAULT_CHUNK_SIZE = 1024 * 256 DEFAULT_CHUNK_SIZE = 1024 * 256
attr_reader :object_id, :chunk_number attr_reader :object_id, :chunk_number
attr_accessor :data attr_accessor :data
def initialize(file, mongo_object={}) def initialize(file, mongo_object={})
@file = file @file = file
@object_id = mongo_object['_id'] || XGen::Mongo::Driver::ObjectID.new @object_id = mongo_object['_id'] || Mongo::ObjectID.new
@chunk_number = mongo_object['n'] || 0 @chunk_number = mongo_object['n'] || 0
@data = ByteBuffer.new @data = ByteBuffer.new
case mongo_object['data'] case mongo_object['data']
when String when String
mongo_object['data'].each_byte { |b| @data.put(b) } mongo_object['data'].each_byte { |b| @data.put(b) }
when ByteBuffer when ByteBuffer
@data.put_array(mongo_object['data'].to_a) @data.put_array(mongo_object['data'].to_a)
when Array when Array
@data.put_array(mongo_object['data']) @data.put_array(mongo_object['data'])
when nil when nil
else else
raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}" raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}"
end end
@data.rewind @data.rewind
end end
def pos; @data.position; end def pos; @data.position; end
def pos=(pos); @data.position = pos; end def pos=(pos); @data.position = pos; end
def eof?; !@data.more?; end def eof?; !@data.more?; end
def size; @data.size; end def size; @data.size; end
alias_method :length, :size alias_method :length, :size
# Erase all data after current position.
def truncate
if @data.position < @data.length
curr_data = @data
@data = ByteBuffer.new
@data.put_array(curr_data.to_a[0...curr_data.position])
end
end
def getc
@data.more? ? @data.get : nil
end
def putc(byte)
@data.put(byte)
end
def save
coll = @file.chunk_collection
coll.remove({'_id' => @object_id})
coll.insert(to_mongo_object)
end
def to_mongo_object
h = OrderedHash.new
h['_id'] = @object_id
h['files_id'] = @file.files_id
h['n'] = @chunk_number
h['data'] = data
h
end
# Erase all data after current position.
def truncate
if @data.position < @data.length
curr_data = @data
@data = ByteBuffer.new
@data.put_array(curr_data.to_a[0...curr_data.position])
end end
end end
def getc
@data.more? ? @data.get : nil
end
def putc(byte)
@data.put(byte)
end
def save
coll = @file.chunk_collection
coll.remove({'_id' => @object_id})
coll.insert(to_mongo_object)
end
def to_mongo_object
h = OrderedHash.new
h['_id'] = @object_id
h['files_id'] = @file.files_id
h['n'] = @chunk_number
h['data'] = data
h
end
end end
end end

View File

@ -18,451 +18,447 @@ require 'mongo/types/objectid'
require 'mongo/util/ordered_hash' require 'mongo/util/ordered_hash'
require 'mongo/gridfs/chunk' require 'mongo/gridfs/chunk'
module XGen module GridFS
module Mongo
module GridFS
# GridStore is an IO-like object that provides input and output for # GridStore is an IO-like object that provides input and output for
# streams of data to Mongo. See Mongo's documentation about GridFS for # streams of data to Mongo. See Mongo's documentation about GridFS for
# storage implementation details. # storage implementation details.
#
# Example code:
#
# require 'mongo/gridfs'
# GridStore.open(database, 'filename', 'w') { |f|
# f.puts "Hello, world!"
# }
# GridStore.open(database, 'filename, 'r') { |f|
# puts f.read # => Hello, world!\n
# }
# GridStore.open(database, 'filename', 'w+') { |f|
# f.puts "But wait, there's more!"
# }
# GridStore.open(database, 'filename, 'r') { |f|
# puts f.read # => Hello, world!\nBut wait, there's more!\n
# }
class GridStore
DEFAULT_ROOT_COLLECTION = 'fs'
DEFAULT_CONTENT_TYPE = 'text/plain'
include Enumerable
attr_accessor :filename
# Array of strings; may be +nil+
attr_accessor :aliases
# Default is DEFAULT_CONTENT_TYPE
attr_accessor :content_type
attr_accessor :metadata
attr_reader :files_id
# Time that the file was first saved.
attr_reader :upload_date
attr_reader :chunk_size
attr_accessor :lineno
attr_reader :md5
class << self
def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
db.collection("#{root_collection}.files").find({'filename' => name}).next_object != nil
end
def open(db, name, mode, options={})
gs = self.new(db, name, mode, options)
result = nil
begin
result = yield gs if block_given?
ensure
gs.close
end
result
end
def read(db, name, length=nil, offset=nil)
GridStore.open(db, name, 'r') { |gs|
gs.seek(offset) if offset
gs.read(length)
}
end
# List the contains of all GridFS files stored in the given db and
# root collection.
# #
# Example code: # :db :: the database to use
# #
# require 'mongo/gridfs' # :root_collection :: the root collection to use
# GridStore.open(database, 'filename', 'w') { |f| def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
# f.puts "Hello, world!" db.collection("#{root_collection}.files").find().map { |f|
# } f['filename']
# GridStore.open(database, 'filename, 'r') { |f| }
# puts f.read # => Hello, world!\n end
# }
# GridStore.open(database, 'filename', 'w+') { |f|
# f.puts "But wait, there's more!"
# }
# GridStore.open(database, 'filename, 'r') { |f|
# puts f.read # => Hello, world!\nBut wait, there's more!\n
# }
class GridStore
DEFAULT_ROOT_COLLECTION = 'fs' def readlines(db, name, separator=$/)
DEFAULT_CONTENT_TYPE = 'text/plain' GridStore.open(db, name, 'r') { |gs|
gs.readlines(separator)
}
end
include Enumerable def unlink(db, *names)
names.each { |name|
gs = GridStore.new(db, name)
gs.send(:delete_chunks)
gs.collection.remove('_id' => gs.files_id)
}
end
alias_method :delete, :unlink
attr_accessor :filename end
# Array of strings; may be +nil+ #---
attr_accessor :aliases # ================================================================
#+++
# Default is DEFAULT_CONTENT_TYPE # Mode may only be 'r', 'w', or 'w+'.
attr_accessor :content_type #
# Options. Descriptions start with a list of the modes for which that
# option is legitimate.
#
# :root :: (r, w, w+) Name of root collection to use, instead of
# DEFAULT_ROOT_COLLECTION.
#
# :metadata:: (w, w+) A hash containing any data you want persisted as
# this file's metadata. See also metadata=
#
# :chunk_size :: (w) Sets chunk size for files opened for writing
# See also chunk_size= which may only be called before
# any data is written.
#
# :content_type :: (w) Default value is DEFAULT_CONTENT_TYPE. See
# also #content_type=
def initialize(db, name, mode='r', options={})
@db, @filename, @mode = db, name, mode
@root = options[:root] || DEFAULT_ROOT_COLLECTION
attr_accessor :metadata doc = collection.find({'filename' => @filename}).next_object
if doc
@files_id = doc['_id']
@content_type = doc['contentType']
@chunk_size = doc['chunkSize']
@upload_date = doc['uploadDate']
@aliases = doc['aliases']
@length = doc['length']
@metadata = doc['metadata']
@md5 = doc['md5']
else
@files_id = Mongo::ObjectID.new
@content_type = DEFAULT_CONTENT_TYPE
@chunk_size = Chunk::DEFAULT_CHUNK_SIZE
@length = 0
end
attr_reader :files_id case mode
when 'r'
@curr_chunk = nth_chunk(0)
@position = 0
when 'w'
chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
delete_chunks
@curr_chunk = Chunk.new(self, 'n' => 0)
@content_type = options[:content_type] if options[:content_type]
@chunk_size = options[:chunk_size] if options[:chunk_size]
@metadata = options[:metadata] if options[:metadata]
@position = 0
when 'w+'
chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
@curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
@curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
@metadata = options[:metadata] if options[:metadata]
@position = @length
else
raise "error: illegal mode #{mode}"
end
# Time that the file was first saved. @lineno = 0
attr_reader :upload_date @pushback_byte = nil
end
attr_reader :chunk_size def collection
@db.collection("#{@root}.files")
end
attr_accessor :lineno # Returns collection used for storing chunks. Depends on value of
# @root.
def chunk_collection
@db.collection("#{@root}.chunks")
end
attr_reader :md5 # Change chunk size. Can only change if the file is opened for write
# and no data has yet been written.
def chunk_size=(size)
unless @mode[0] == ?w && @position == 0 && @upload_date == nil
raise "error: can only change chunk size if open for write and no data written."
end
@chunk_size = size
end
class << self #---
# ================ reading ================
def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION) #+++
db.collection("#{root_collection}.files").find({'filename' => name}).next_object != nil
end
def open(db, name, mode, options={})
gs = self.new(db, name, mode, options)
result = nil
begin
result = yield gs if block_given?
ensure
gs.close
end
result
end
def read(db, name, length=nil, offset=nil)
GridStore.open(db, name, 'r') { |gs|
gs.seek(offset) if offset
gs.read(length)
}
end
# List the contains of all GridFS files stored in the given db and
# root collection.
#
# :db :: the database to use
#
# :root_collection :: the root collection to use
def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
db.collection("#{root_collection}.files").find().map { |f|
f['filename']
}
end
def readlines(db, name, separator=$/)
GridStore.open(db, name, 'r') { |gs|
gs.readlines(separator)
}
end
def unlink(db, *names)
names.each { |name|
gs = GridStore.new(db, name)
gs.send(:delete_chunks)
gs.collection.remove('_id' => gs.files_id)
}
end
alias_method :delete, :unlink
def getc
if @pushback_byte
byte = @pushback_byte
@pushback_byte = nil
@position += 1
byte
elsif eof?
nil
else
if @curr_chunk.eof?
@curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
end end
@position += 1
#--- @curr_chunk.getc
# ================================================================
#+++
# Mode may only be 'r', 'w', or 'w+'.
#
# Options. Descriptions start with a list of the modes for which that
# option is legitimate.
#
# :root :: (r, w, w+) Name of root collection to use, instead of
# DEFAULT_ROOT_COLLECTION.
#
# :metadata:: (w, w+) A hash containing any data you want persisted as
# this file's metadata. See also metadata=
#
# :chunk_size :: (w) Sets chunk size for files opened for writing
# See also chunk_size= which may only be called before
# any data is written.
#
# :content_type :: (w) Default value is DEFAULT_CONTENT_TYPE. See
# also #content_type=
def initialize(db, name, mode='r', options={})
@db, @filename, @mode = db, name, mode
@root = options[:root] || DEFAULT_ROOT_COLLECTION
doc = collection.find({'filename' => @filename}).next_object
if doc
@files_id = doc['_id']
@content_type = doc['contentType']
@chunk_size = doc['chunkSize']
@upload_date = doc['uploadDate']
@aliases = doc['aliases']
@length = doc['length']
@metadata = doc['metadata']
@md5 = doc['md5']
else
@files_id = XGen::Mongo::Driver::ObjectID.new
@content_type = DEFAULT_CONTENT_TYPE
@chunk_size = Chunk::DEFAULT_CHUNK_SIZE
@length = 0
end
case mode
when 'r'
@curr_chunk = nth_chunk(0)
@position = 0
when 'w'
chunk_collection.create_index([['files_id', XGen::Mongo::ASCENDING], ['n', XGen::Mongo::ASCENDING]])
delete_chunks
@curr_chunk = Chunk.new(self, 'n' => 0)
@content_type = options[:content_type] if options[:content_type]
@chunk_size = options[:chunk_size] if options[:chunk_size]
@metadata = options[:metadata] if options[:metadata]
@position = 0
when 'w+'
chunk_collection.create_index([['files_id', XGen::Mongo::ASCENDING], ['n', XGen::Mongo::ASCENDING]])
@curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
@curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
@metadata = options[:metadata] if options[:metadata]
@position = @length
else
raise "error: illegal mode #{mode}"
end
@lineno = 0
@pushback_byte = nil
end
def collection
@db.collection("#{@root}.files")
end
# Returns collection used for storing chunks. Depends on value of
# @root.
def chunk_collection
@db.collection("#{@root}.chunks")
end
# Change chunk size. Can only change if the file is opened for write
# and no data has yet been written.
def chunk_size=(size)
unless @mode[0] == ?w && @position == 0 && @upload_date == nil
raise "error: can only change chunk size if open for write and no data written."
end
@chunk_size = size
end
#---
# ================ reading ================
#+++
def getc
if @pushback_byte
byte = @pushback_byte
@pushback_byte = nil
@position += 1
byte
elsif eof?
nil
else
if @curr_chunk.eof?
@curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
end
@position += 1
@curr_chunk.getc
end
end
def gets(separator=$/)
str = ''
byte = self.getc
return nil if byte == nil # EOF
while byte != nil
s = byte.chr
str << s
break if s == separator
byte = self.getc
end
@lineno += 1
str
end
def read(len=nil, buf=nil)
buf ||= ''
byte = self.getc
while byte != nil && (len == nil || len > 0)
buf << byte.chr
len -= 1 if len
byte = self.getc if (len == nil || len > 0)
end
buf
end
def readchar
byte = self.getc
raise EOFError.new if byte == nil
byte
end
def readline(separator=$/)
line = gets
raise EOFError.new if line == nil
line
end
def readlines(separator=$/)
read.split(separator).collect { |line| "#{line}#{separator}" }
end
def each
line = gets
while line
yield line
line = gets
end
end
alias_method :each_line, :each
def each_byte
byte = self.getc
while byte
yield byte
byte = self.getc
end
end
def ungetc(byte)
@pushback_byte = byte
@position -= 1
end
#---
# ================ writing ================
#+++
def putc(byte)
if @curr_chunk.pos == @chunk_size
prev_chunk_number = @curr_chunk.chunk_number
@curr_chunk.save
@curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
end
@position += 1
@curr_chunk.putc(byte)
end
def print(*objs)
objs = [$_] if objs == nil || objs.empty?
objs.each { |obj|
str = obj.to_s
str.each_byte { |byte| self.putc(byte) }
}
nil
end
def puts(*objs)
if objs == nil || objs.empty?
self.putc(10)
else
print(*objs.collect{ |obj|
str = obj.to_s
str << "\n" unless str =~ /\n$/
str
})
end
nil
end
def <<(obj)
write(obj.to_s)
end
# Writes +string+ as bytes and returns the number of bytes written.
def write(string)
raise "#@filename not opened for write" unless @mode[0] == ?w
count = 0
string.each_byte { |byte|
self.putc byte
count += 1
}
count
end
# A no-op.
def flush
end
#---
# ================ status ================
#+++
def eof
raise IOError.new("stream not open for reading") unless @mode[0] == ?r
@position >= @length
end
alias_method :eof?, :eof
#---
# ================ positioning ================
#+++
def rewind
if @curr_chunk.chunk_number != 0
if @mode[0] == ?w
delete_chunks
@curr_chunk = Chunk.new(self, 'n' => 0)
else
@curr_chunk == nth_chunk(0)
end
end
@curr_chunk.pos = 0
@lineno = 0
@position = 0
end
def seek(pos, whence=IO::SEEK_SET)
target_pos = case whence
when IO::SEEK_CUR
@position + pos
when IO::SEEK_END
@length + pos
when IO::SEEK_SET
pos
end
new_chunk_number = (target_pos / @chunk_size).to_i
if new_chunk_number != @curr_chunk.chunk_number
@curr_chunk.save if @mode[0] == ?w
@curr_chunk = nth_chunk(new_chunk_number)
end
@position = target_pos
@curr_chunk.pos = @position % @chunk_size
0
end
def tell
@position
end
#---
# ================ closing ================
#+++
def close
if @mode[0] == ?w
if @curr_chunk
@curr_chunk.truncate
@curr_chunk.save if @curr_chunk.pos > 0
end
files = collection
if @upload_date
files.remove('_id' => @files_id)
else
@upload_date = Time.now
end
files.insert(to_mongo_object)
end
@db = nil
end
def closed?
@db == nil
end
#---
# ================ protected ================
#+++
protected
def to_mongo_object
h = OrderedHash.new
h['_id'] = @files_id
h['filename'] = @filename
h['contentType'] = @content_type
h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
h['chunkSize'] = @chunk_size
h['uploadDate'] = @upload_date
h['aliases'] = @aliases
h['metadata'] = @metadata
md5_command = OrderedHash.new
md5_command['filemd5'] = @files_id
md5_command['root'] = @root
h['md5'] = @db.db_command(md5_command)['md5']
h
end
def delete_chunks
chunk_collection.remove({'files_id' => @files_id}) if @files_id
@curr_chunk = nil
end
def nth_chunk(n)
mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_object
Chunk.new(self, mongo_chunk || {})
end
def last_chunk_number
(@length / @chunk_size).to_i
end
end end
end end
def gets(separator=$/)
str = ''
byte = self.getc
return nil if byte == nil # EOF
while byte != nil
s = byte.chr
str << s
break if s == separator
byte = self.getc
end
@lineno += 1
str
end
def read(len=nil, buf=nil)
buf ||= ''
byte = self.getc
while byte != nil && (len == nil || len > 0)
buf << byte.chr
len -= 1 if len
byte = self.getc if (len == nil || len > 0)
end
buf
end
def readchar
byte = self.getc
raise EOFError.new if byte == nil
byte
end
def readline(separator=$/)
line = gets
raise EOFError.new if line == nil
line
end
def readlines(separator=$/)
read.split(separator).collect { |line| "#{line}#{separator}" }
end
def each
line = gets
while line
yield line
line = gets
end
end
alias_method :each_line, :each
def each_byte
byte = self.getc
while byte
yield byte
byte = self.getc
end
end
def ungetc(byte)
@pushback_byte = byte
@position -= 1
end
#---
# ================ writing ================
#+++
def putc(byte)
if @curr_chunk.pos == @chunk_size
prev_chunk_number = @curr_chunk.chunk_number
@curr_chunk.save
@curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
end
@position += 1
@curr_chunk.putc(byte)
end
def print(*objs)
objs = [$_] if objs == nil || objs.empty?
objs.each { |obj|
str = obj.to_s
str.each_byte { |byte| self.putc(byte) }
}
nil
end
def puts(*objs)
if objs == nil || objs.empty?
self.putc(10)
else
print(*objs.collect{ |obj|
str = obj.to_s
str << "\n" unless str =~ /\n$/
str
})
end
nil
end
def <<(obj)
write(obj.to_s)
end
# Writes +string+ as bytes and returns the number of bytes written.
def write(string)
raise "#@filename not opened for write" unless @mode[0] == ?w
count = 0
string.each_byte { |byte|
self.putc byte
count += 1
}
count
end
# A no-op.
def flush
end
#---
# ================ status ================
#+++
def eof
raise IOError.new("stream not open for reading") unless @mode[0] == ?r
@position >= @length
end
alias_method :eof?, :eof
#---
# ================ positioning ================
#+++
def rewind
if @curr_chunk.chunk_number != 0
if @mode[0] == ?w
delete_chunks
@curr_chunk = Chunk.new(self, 'n' => 0)
else
@curr_chunk == nth_chunk(0)
end
end
@curr_chunk.pos = 0
@lineno = 0
@position = 0
end
def seek(pos, whence=IO::SEEK_SET)
target_pos = case whence
when IO::SEEK_CUR
@position + pos
when IO::SEEK_END
@length + pos
when IO::SEEK_SET
pos
end
new_chunk_number = (target_pos / @chunk_size).to_i
if new_chunk_number != @curr_chunk.chunk_number
@curr_chunk.save if @mode[0] == ?w
@curr_chunk = nth_chunk(new_chunk_number)
end
@position = target_pos
@curr_chunk.pos = @position % @chunk_size
0
end
def tell
@position
end
#---
# ================ closing ================
#+++
def close
if @mode[0] == ?w
if @curr_chunk
@curr_chunk.truncate
@curr_chunk.save if @curr_chunk.pos > 0
end
files = collection
if @upload_date
files.remove('_id' => @files_id)
else
@upload_date = Time.now
end
files.insert(to_mongo_object)
end
@db = nil
end
def closed?
@db == nil
end
#---
# ================ protected ================
#+++
protected
def to_mongo_object
h = OrderedHash.new
h['_id'] = @files_id
h['filename'] = @filename
h['contentType'] = @content_type
h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
h['chunkSize'] = @chunk_size
h['uploadDate'] = @upload_date
h['aliases'] = @aliases
h['metadata'] = @metadata
md5_command = OrderedHash.new
md5_command['filemd5'] = @files_id
md5_command['root'] = @root
h['md5'] = @db.db_command(md5_command)['md5']
h
end
def delete_chunks
chunk_collection.remove({'files_id' => @files_id}) if @files_id
@curr_chunk = nil
end
def nth_chunk(n)
mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_object
Chunk.new(self, mongo_chunk || {})
end
def last_chunk_number
(@length / @chunk_size).to_i
end
end end
end end

View File

@ -17,21 +17,16 @@
require 'mongo/message/message' require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
module XGen module Mongo
module Mongo
module Driver
class GetMoreMessage < Message class GetMoreMessage < Message
def initialize(db_name, collection_name, cursor) def initialize(db_name, collection_name, cursor)
super(OP_GET_MORE) super(OP_GET_MORE)
write_int(0) write_int(0)
write_string("#{db_name}.#{collection_name}") write_string("#{db_name}.#{collection_name}")
write_int(0) # num to return; leave it up to the db for now write_int(0) # num to return; leave it up to the db for now
write_long(cursor) write_long(cursor)
end
end
end end
end end
end end

View File

@ -17,19 +17,15 @@
require 'mongo/message/message' require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
module XGen module Mongo
module Mongo
module Driver
class InsertMessage < Message class InsertMessage < Message
def initialize(db_name, collection_name, check_keys=true, *objs) def initialize(db_name, collection_name, check_keys=true, *objs)
super(OP_INSERT) super(OP_INSERT)
write_int(0) write_int(0)
write_string("#{db_name}.#{collection_name}") write_string("#{db_name}.#{collection_name}")
objs.each { |o| write_doc(o, check_keys) } objs.each { |o| write_doc(o, check_keys) }
end
end
end end
end end
end end

View File

@ -17,20 +17,15 @@
require 'mongo/message/message' require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
module XGen module Mongo
module Mongo
module Driver
class KillCursorsMessage < Message class KillCursorsMessage < Message
def initialize(*cursors) def initialize(*cursors)
super(OP_KILL_CURSORS) super(OP_KILL_CURSORS)
write_int(0) write_int(0)
write_int(cursors.length) write_int(cursors.length)
cursors.each { |c| write_long c } cursors.each { |c| write_long c }
end
end
end end
end end
end end

View File

@ -17,68 +17,64 @@
require 'mongo/util/bson' require 'mongo/util/bson'
require 'mongo/util/byte_buffer' require 'mongo/util/byte_buffer'
module XGen module Mongo
module Mongo
module Driver
class Message class Message
HEADER_SIZE = 16 # size, id, response_to, opcode HEADER_SIZE = 16 # size, id, response_to, opcode
@@class_req_id = 0 @@class_req_id = 0
attr_reader :buf # for testing attr_reader :buf # for testing
def initialize(op) def initialize(op)
@op = op @op = op
@message_length = HEADER_SIZE @message_length = HEADER_SIZE
@data_length = 0 @data_length = 0
@request_id = (@@class_req_id += 1) @request_id = (@@class_req_id += 1)
@response_id = 0 @response_id = 0
@buf = ByteBuffer.new @buf = ByteBuffer.new
@buf.put_int(16) # holder for length @buf.put_int(16) # holder for length
@buf.put_int(@request_id) @buf.put_int(@request_id)
@buf.put_int(0) # response_to @buf.put_int(0) # response_to
@buf.put_int(op) @buf.put_int(op)
end
def write_int(i)
@buf.put_int(i)
update_message_length
end
def write_long(i)
@buf.put_long(i)
update_message_length
end
def write_string(s)
BSON.serialize_cstr(@buf, s)
update_message_length
end
def write_doc(hash, check_keys=false)
@buf.put_array(BSON.new.serialize(hash, check_keys).to_a)
update_message_length
end
def to_a
@buf.to_a
end
def dump
@buf.dump
end
# Do not call. Private, but kept public for testing.
def update_message_length
pos = @buf.position
@buf.put_int(@buf.size, 0)
@buf.position = pos
end
end
end end
def write_int(i)
@buf.put_int(i)
update_message_length
end
def write_long(i)
@buf.put_long(i)
update_message_length
end
def write_string(s)
BSON.serialize_cstr(@buf, s)
update_message_length
end
def write_doc(hash, check_keys=false)
@buf.put_array(BSON.new.serialize(hash, check_keys).to_a)
update_message_length
end
def to_a
@buf.to_a
end
def dump
@buf.dump
end
# Do not call. Private, but kept public for testing.
def update_message_length
pos = @buf.position
@buf.put_int(@buf.size, 0)
@buf.position = pos
end
end end
end end

View File

@ -16,35 +16,30 @@
require 'mongo/util/byte_buffer' require 'mongo/util/byte_buffer'
module XGen module Mongo
module Mongo
module Driver
class MessageHeader class MessageHeader
HEADER_SIZE = 16 HEADER_SIZE = 16
def initialize() def initialize()
@buf = ByteBuffer.new @buf = ByteBuffer.new
end end
def read_header(db) def read_header(db)
@buf.rewind @buf.rewind
@buf.put_array(db.receive_full(HEADER_SIZE).unpack("C*")) @buf.put_array(db.receive_full(HEADER_SIZE).unpack("C*"))
raise "Short read for DB response header: expected #{HEADER_SIZE} bytes, saw #{@buf.size}" unless @buf.size == HEADER_SIZE raise "Short read for DB response header: expected #{HEADER_SIZE} bytes, saw #{@buf.size}" unless @buf.size == HEADER_SIZE
@buf.rewind @buf.rewind
@size = @buf.get_int @size = @buf.get_int
@request_id = @buf.get_int @request_id = @buf.get_int
@response_to = @buf.get_int @response_to = @buf.get_int
@op = @buf.get_int @op = @buf.get_int
self self
end end
def dump def dump
@buf.dump @buf.dump
end
end
end end
end end
end end

View File

@ -17,17 +17,13 @@
require 'mongo/message/message' require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
module XGen module Mongo
module Mongo
module Driver
class MsgMessage < Message class MsgMessage < Message
def initialize(msg) def initialize(msg)
super(OP_MSG) super(OP_MSG)
write_string(msg) write_string(msg)
end
end
end end
end end
end end

View File

@ -14,19 +14,14 @@
# limitations under the License. # limitations under the License.
# ++ # ++
module XGen module Mongo
module Mongo OP_REPLY = 1 # reply. responseTo is set.
module Driver OP_MSG = 1000 # generic msg command followed by a string
OP_REPLY = 1 # reply. responseTo is set. OP_UPDATE = 2001 # update object
OP_MSG = 1000 # generic msg command followed by a string OP_INSERT = 2002
OP_UPDATE = 2001 # update object # GET_BY_OID = 2003
OP_INSERT = 2002 OP_QUERY = 2004
# GET_BY_OID = 2003 OP_GET_MORE = 2005
OP_QUERY = 2004 OP_DELETE = 2006
OP_GET_MORE = 2005 OP_KILL_CURSORS = 2007
OP_DELETE = 2006
OP_KILL_CURSORS = 2007
end
end
end end

View File

@ -18,60 +18,56 @@ require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
require 'mongo/util/ordered_hash' require 'mongo/util/ordered_hash'
module XGen module Mongo
module Mongo
module Driver
class QueryMessage < Message class QueryMessage < Message
attr_reader :query attr_reader :query
def initialize(db_name, collection_name, query) def initialize(db_name, collection_name, query)
super(OP_QUERY) super(OP_QUERY)
@query = query @query = query
write_int(0) write_int(0)
write_string("#{db_name}.#{collection_name}") write_string("#{db_name}.#{collection_name}")
write_int(query.number_to_skip) write_int(query.number_to_skip)
write_int(-query.number_to_return) # Negative means hard limit write_int(-query.number_to_return) # Negative means hard limit
sel = query.selector sel = query.selector
if query.contains_special_fields if query.contains_special_fields
sel = OrderedHash.new sel = OrderedHash.new
sel['query'] = query.selector sel['query'] = query.selector
if query.order_by && query.order_by.length > 0 if query.order_by && query.order_by.length > 0
sel['orderby'] = case query.order_by sel['orderby'] = case query.order_by
when String
{query.order_by => 1}
when Array
h = OrderedHash.new
query.order_by.each { |ob|
case ob
when String when String
{query.order_by => 1} h[ob] = 1
when Array when Hash # should have one entry; will handle all
h = OrderedHash.new ob.each { |k,v| h[k] = v }
query.order_by.each { |ob|
case ob
when String
h[ob] = 1
when Hash # should have one entry; will handle all
ob.each { |k,v| h[k] = v }
else
raise "illegal query order_by value #{query.order_by.inspect}"
end
}
h
when Hash # Should be an ordered hash, but this message doesn't care
query.order_by
else else
raise "illegal order_by: is a #{query.order_by.class.name}, must be String, Array, Hash, or OrderedHash" raise "illegal query order_by value #{query.order_by.inspect}"
end end
end }
sel['$hint'] = query.hint if query.hint && query.hint.length > 0 h
sel['$explain'] = true if query.explain when Hash # Should be an ordered hash, but this message doesn't care
sel['$snapshot'] = true if query.snapshot query.order_by
end else
write_doc(sel) raise "illegal order_by: is a #{query.order_by.class.name}, must be String, Array, Hash, or OrderedHash"
write_doc(query.fields) if query.fields end
end
def first_key(key)
@first_key = key
end end
sel['$hint'] = query.hint if query.hint && query.hint.length > 0
sel['$explain'] = true if query.explain
sel['$snapshot'] = true if query.snapshot
end end
write_doc(sel)
write_doc(query.fields) if query.fields
end
def first_key(key)
@first_key = key
end end
end end
end end

View File

@ -17,20 +17,16 @@
require 'mongo/message/message' require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
module XGen module Mongo
module Mongo
module Driver
class RemoveMessage < Message class RemoveMessage < Message
def initialize(db_name, collection_name, sel) def initialize(db_name, collection_name, sel)
super(OP_DELETE) super(OP_DELETE)
write_int(0) write_int(0)
write_string("#{db_name}.#{collection_name}") write_string("#{db_name}.#{collection_name}")
write_int(0) # flags? write_int(0) # flags?
write_doc(sel) write_doc(sel)
end
end
end end
end end
end end

View File

@ -17,21 +17,17 @@
require 'mongo/message/message' require 'mongo/message/message'
require 'mongo/message/opcodes' require 'mongo/message/opcodes'
module XGen module Mongo
module Mongo
module Driver
class UpdateMessage < Message class UpdateMessage < Message
def initialize(db_name, collection_name, sel, obj, repsert) def initialize(db_name, collection_name, sel, obj, repsert)
super(OP_UPDATE) super(OP_UPDATE)
write_int(0) write_int(0)
write_string("#{db_name}.#{collection_name}") write_string("#{db_name}.#{collection_name}")
write_int(repsert ? 1 : 0) # 1 if a repsert operation (upsert) write_int(repsert ? 1 : 0) # 1 if a repsert operation (upsert)
write_doc(sel) write_doc(sel)
write_doc(obj) write_doc(obj)
end
end
end end
end end
end end

View File

@ -16,149 +16,144 @@
require 'mongo/db' require 'mongo/db'
module XGen module Mongo
module Mongo
module Driver
# Represents a Mongo database server. # Represents a Mongo database server.
class Mongo class Mongo
DEFAULT_PORT = 27017 DEFAULT_PORT = 27017
# Create a Mongo database server instance. You specify either one or a # Create a Mongo database server instance. You specify either one or a
# pair of servers. If one, you also say if connecting to a slave is # pair of servers. If one, you also say if connecting to a slave is
# OK. In either case, the host default is "localhost" and port default # OK. In either case, the host default is "localhost" and port default
# is DEFAULT_PORT. # is DEFAULT_PORT.
# #
# If you specify a pair, pair_or_host is a hash with two keys :left # If you specify a pair, pair_or_host is a hash with two keys :left
# and :right. Each key maps to either # and :right. Each key maps to either
# * a server name, in which case port is DEFAULT_PORT # * a server name, in which case port is DEFAULT_PORT
# * a port number, in which case server is "localhost" # * a port number, in which case server is "localhost"
# * an array containing a server name and a port number in that order # * an array containing a server name and a port number in that order
# #
# +options+ are passed on to each DB instance: # +options+ are passed on to each DB instance:
# #
# :slave_ok :: Only used if one host is specified. If false, when # :slave_ok :: Only used if one host is specified. If false, when
# connecting to that host/port a DB object will check to # connecting to that host/port a DB object will check to
# see if the server is the master. If it is not, an error # see if the server is the master. If it is not, an error
# is thrown. # is thrown.
# #
# :auto_reconnect :: If a DB connection gets closed (for example, we # :auto_reconnect :: If a DB connection gets closed (for example, we
# have a server pair and saw the "not master" # have a server pair and saw the "not master"
# error, which closes the connection), then # error, which closes the connection), then
# automatically try to reconnect to the master or # automatically try to reconnect to the master or
# to the single server we have been given. Defaults # to the single server we have been given. Defaults
# to +false+. # to +false+.
# #
# Since that's so confusing, here are a few examples: # Since that's so confusing, here are a few examples:
# #
# Mongo.new # localhost, DEFAULT_PORT, !slave # Mongo.new # localhost, DEFAULT_PORT, !slave
# Mongo.new("localhost") # localhost, DEFAULT_PORT, !slave # Mongo.new("localhost") # localhost, DEFAULT_PORT, !slave
# Mongo.new("localhost", 3000) # localhost, 3000, slave not ok # Mongo.new("localhost", 3000) # localhost, 3000, slave not ok
# # localhost, 3000, slave ok # # localhost, 3000, slave ok
# Mongo.new("localhost", 3000, :slave_ok => true) # Mongo.new("localhost", 3000, :slave_ok => true)
# # localhost, DEFAULT_PORT, auto reconnect # # localhost, DEFAULT_PORT, auto reconnect
# Mongo.new(nil, nil, :auto_reconnect => true) # Mongo.new(nil, nil, :auto_reconnect => true)
# #
# # A pair of servers. DB will always talk to the master. On socket # # A pair of servers. DB will always talk to the master. On socket
# # error or "not master" error, we will auto-reconnect to the # # error or "not master" error, we will auto-reconnect to the
# # current master. # # current master.
# Mongo.new({:left => ["db1.example.com", 3000], # Mongo.new({:left => ["db1.example.com", 3000],
# :right => "db2.example.com"}, # DEFAULT_PORT # :right => "db2.example.com"}, # DEFAULT_PORT
# nil, :auto_reconnect => true) # nil, :auto_reconnect => true)
# #
# # Here, :right is localhost/DEFAULT_PORT. No auto-reconnect. # # Here, :right is localhost/DEFAULT_PORT. No auto-reconnect.
# Mongo.new({:left => ["db1.example.com", 3000]}) # Mongo.new({:left => ["db1.example.com", 3000]})
# #
# When a DB object first connects to a pair, it will find the master # When a DB object first connects to a pair, it will find the master
# instance and connect to that one. # instance and connect to that one.
def initialize(pair_or_host=nil, port=nil, options={}) def initialize(pair_or_host=nil, port=nil, options={})
@pair = case pair_or_host @pair = case pair_or_host
when String when String
[[pair_or_host, port ? port.to_i : DEFAULT_PORT]] [[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
when Hash when Hash
connections = [] connections = []
connections << pair_val_to_connection(pair_or_host[:left]) connections << pair_val_to_connection(pair_or_host[:left])
connections << pair_val_to_connection(pair_or_host[:right]) connections << pair_val_to_connection(pair_or_host[:right])
connections connections
when nil when nil
[['localhost', DEFAULT_PORT]] [['localhost', DEFAULT_PORT]]
end end
@options = options @options = options
end end
# Return the XGen::Mongo::Driver::DB named +db_name+. The slave_ok and # Return the Mongo::DB named +db_name+. The slave_ok and
# auto_reconnect options passed in via #new may be overridden here. # auto_reconnect options passed in via #new may be overridden here.
# See DB#new for other options you can pass in. # See DB#new for other options you can pass in.
def db(db_name, options={}) def db(db_name, options={})
XGen::Mongo::Driver::DB.new(db_name, @pair, @options.merge(options)) DB.new(db_name, @pair, @options.merge(options))
end end
# Returns a hash containing database names as keys and disk space for # Returns a hash containing database names as keys and disk space for
# each as values. # each as values.
def database_info def database_info
doc = single_db_command('admin', :listDatabases => 1) doc = single_db_command('admin', :listDatabases => 1)
h = {} h = {}
doc['databases'].each { |db| doc['databases'].each { |db|
h[db['name']] = db['sizeOnDisk'].to_i h[db['name']] = db['sizeOnDisk'].to_i
} }
h h
end end
# Returns an array of database names. # Returns an array of database names.
def database_names def database_names
database_info.keys database_info.keys
end end
# Not implemented. # Not implemented.
def clone_database(from) def clone_database(from)
raise "not implemented" raise "not implemented"
end end
# Not implemented. # Not implemented.
def copy_database(from_host, from_db, to_db) def copy_database(from_host, from_db, to_db)
raise "not implemented" raise "not implemented"
end end
# Drops the database +name+. # Drops the database +name+.
def drop_database(name) def drop_database(name)
single_db_command(name, :dropDatabase => 1) single_db_command(name, :dropDatabase => 1)
end end
protected protected
# Turns an array containing a host name string and a
# port number integer into a [host, port] pair array.
def pair_val_to_connection(a)
case a
when nil
['localhost', DEFAULT_PORT]
when String
[a, DEFAULT_PORT]
when Integer
['localhost', a]
when Array
a
end
end
# Send cmd (a hash, possibly ordered) to the admin database and return
# the answer. Raises an error unless the return is "ok" (DB#ok?
# returns +true+).
def single_db_command(db_name, cmd)
db = nil
begin
db = db(db_name)
doc = db.db_command(cmd)
raise "error retrieving database info: #{doc.inspect}" unless db.ok?(doc)
doc
ensure
db.close if db
end
end
# Turns an array containing a host name string and a
# port number integer into a [host, port] pair array.
def pair_val_to_connection(a)
case a
when nil
['localhost', DEFAULT_PORT]
when String
[a, DEFAULT_PORT]
when Integer
['localhost', a]
when Array
a
end end
end end
# Send cmd (a hash, possibly ordered) to the admin database and return
# the answer. Raises an error unless the return is "ok" (DB#ok?
# returns +true+).
def single_db_command(db_name, cmd)
db = nil
begin
db = db(db_name)
doc = db.db_command(cmd)
raise "error retrieving database info: #{doc.inspect}" unless db.ok?(doc)
doc
ensure
db.close if db
end
end
end end
end end

View File

@ -18,102 +18,98 @@ require 'mongo/collection'
require 'mongo/message' require 'mongo/message'
require 'mongo/types/code' require 'mongo/types/code'
module XGen module Mongo
module Mongo
module Driver
# A query against a collection. A query's selector is a hash. See the # A query against a collection. A query's selector is a hash. See the
# Mongo documentation for query details. # Mongo documentation for query details.
class Query class Query
attr_accessor :number_to_skip, :number_to_return, :order_by, :snapshot attr_accessor :number_to_skip, :number_to_return, :order_by, :snapshot
# If true, $explain will be set in QueryMessage that uses this query. # If true, $explain will be set in QueryMessage that uses this query.
attr_accessor :explain attr_accessor :explain
# Either +nil+ or a hash (preferably an OrderedHash). # Either +nil+ or a hash (preferably an OrderedHash).
attr_accessor :hint attr_accessor :hint
attr_reader :selector # writer defined below attr_reader :selector # writer defined below
# sel :: A hash describing the query. See the Mongo docs for details. # sel :: A hash describing the query. See the Mongo docs for details.
# #
# return_fields :: If not +nil+, a single field name or an array of # return_fields :: If not +nil+, a single field name or an array of
# field names. Only those fields will be returned. # field names. Only those fields will be returned.
# (Called :fields in calls to Collection#find.) # (Called :fields in calls to Collection#find.)
# #
# number_to_skip :: Number of records to skip before returning # number_to_skip :: Number of records to skip before returning
# records. (Called :offset in calls to # records. (Called :offset in calls to
# Collection#find.) Default is 0. # Collection#find.) Default is 0.
# #
# number_to_return :: Max number of records to return. (Called :limit # number_to_return :: Max number of records to return. (Called :limit
# in calls to Collection#find.) Default is 0 (all # in calls to Collection#find.) Default is 0 (all
# records). # records).
# #
# order_by :: If not +nil+, specifies record sort order. May be a # order_by :: If not +nil+, specifies record sort order. May be a
# String, Hash, OrderedHash, or Array. If a string, the # String, Hash, OrderedHash, or Array. If a string, the
# results will be ordered by that field in ascending # results will be ordered by that field in ascending
# order. If an array, it should be an array of field names # order. If an array, it should be an array of field names
# which will all be sorted in ascending order. If a hash, # which will all be sorted in ascending order. If a hash,
# it may be either a regular Hash or an OrderedHash. The # it may be either a regular Hash or an OrderedHash. The
# keys should be field names, and the values should be 1 # keys should be field names, and the values should be 1
# (ascending) or -1 (descending). Note that if it is a # (ascending) or -1 (descending). Note that if it is a
# regular Hash then sorting by more than one field # regular Hash then sorting by more than one field
# probably will not be what you intend because key order # probably will not be what you intend because key order
# is not preserved. (order_by is called :sort in calls to # is not preserved. (order_by is called :sort in calls to
# Collection#find.) # Collection#find.)
# #
# hint :: If not +nil+, specifies query hint fields. Must be either # hint :: If not +nil+, specifies query hint fields. Must be either
# +nil+ or a hash (preferably an OrderedHash). See # +nil+ or a hash (preferably an OrderedHash). See
# Collection#hint. # Collection#hint.
def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil, hint=nil, snapshot=nil) def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil, hint=nil, snapshot=nil)
@number_to_skip, @number_to_return, @order_by, @hint, @snapshot = @number_to_skip, @number_to_return, @order_by, @hint, @snapshot =
number_to_skip, number_to_return, order_by, hint, snapshot number_to_skip, number_to_return, order_by, hint, snapshot
@explain = nil @explain = nil
self.selector = sel self.selector = sel
self.fields = return_fields self.fields = return_fields
end end
# Set query selector hash. If sel is Code/string, it will be used as a # Set query selector hash. If sel is Code/string, it will be used as a
# $where clause. (See Mongo docs for details.) # $where clause. (See Mongo docs for details.)
def selector=(sel) def selector=(sel)
@selector = case sel @selector = case sel
when nil when nil
{} {}
when Code when Code
{"$where" => sel} {"$where" => sel}
when String when String
{"$where" => Code.new(sel)} {"$where" => Code.new(sel)}
when Hash when Hash
sel sel
end end
end end
# Set fields to return. If +val+ is +nil+ or empty, all fields will be # Set fields to return. If +val+ is +nil+ or empty, all fields will be
# returned. # returned.
def fields=(val) def fields=(val)
@fields = val @fields = val
@fields = nil if @fields && @fields.empty? @fields = nil if @fields && @fields.empty?
end end
def fields def fields
case @fields case @fields
when String when String
{@fields => 1} {@fields => 1}
when Array when Array
if @fields.length == 0 if @fields.length == 0
nil nil
else else
h = {} h = {}
@fields.each { |field| h[field] = 1 } @fields.each { |field| h[field] = 1 }
h h
end
else # nil, anything else
nil
end
end
def contains_special_fields
(@order_by != nil && @order_by.length > 0) || @explain || @hint || @snapshot
end end
else # nil, anything else
nil
end end
end end
def contains_special_fields
(@order_by != nil && @order_by.length > 0) || @explain || @hint || @snapshot
end
end end
end end

View File

@ -16,27 +16,23 @@
require 'mongo/util/byte_buffer' require 'mongo/util/byte_buffer'
module XGen module Mongo
module Mongo
module Driver
# An array of binary bytes with a Mongo subtype value. # An array of binary bytes with a Mongo subtype value.
class Binary < ByteBuffer class Binary < ByteBuffer
SUBTYPE_BYTES = 0x02 SUBTYPE_BYTES = 0x02
SUBTYPE_UUID = 0x03 SUBTYPE_UUID = 0x03
SUBTYPE_MD5 = 0x05 SUBTYPE_MD5 = 0x05
SUBTYPE_USER_DEFINED = 0x80 SUBTYPE_USER_DEFINED = 0x80
# One of the SUBTYPE_* constants. Default is SUBTYPE_BYTES. # One of the SUBTYPE_* constants. Default is SUBTYPE_BYTES.
attr_accessor :subtype attr_accessor :subtype
def initialize(initial_data=[], subtype=SUBTYPE_BYTES) def initialize(initial_data=[], subtype=SUBTYPE_BYTES)
super(initial_data) super(initial_data)
@subtype = subtype @subtype = subtype
end
end
end end
end end
end end

View File

@ -14,21 +14,17 @@
# limitations under the License. # limitations under the License.
# ++ # ++
module XGen module Mongo
module Mongo
module Driver
# JavaScript code to be evaluated by MongoDB # JavaScript code to be evaluated by MongoDB
class Code < String class Code < String
# Hash mapping identifiers to their values # Hash mapping identifiers to their values
attr_accessor :scope attr_accessor :scope
def initialize(code, scope={}) def initialize(code, scope={})
super(code) super(code)
@scope = scope @scope = scope
end
end
end end
end end
end end

View File

@ -14,24 +14,20 @@
# limitations under the License. # limitations under the License.
# ++ # ++
module XGen module Mongo
module Mongo
module Driver
class DBRef class DBRef
attr_reader :namespace, :object_id attr_reader :namespace, :object_id
def initialize(namespace, object_id) def initialize(namespace, object_id)
@namespace, @object_id = @namespace, @object_id =
namespace, object_id namespace, object_id
end
def to_s
"ns: #{namespace}, id: #{object_id}"
end
end
end end
def to_s
"ns: #{namespace}, id: #{object_id}"
end
end end
end end

View File

@ -17,121 +17,117 @@
require 'mutex_m' require 'mutex_m'
require 'mongo/util/byte_buffer' require 'mongo/util/byte_buffer'
module XGen module Mongo
module Mongo
module Driver
# Implementation of the Babble OID. Object ids are not required by # Implementation of the Babble OID. Object ids are not required by
# Mongo, but they make certain operations more efficient. # Mongo, but they make certain operations more efficient.
# #
# The driver does not automatically assign ids to records that are # The driver does not automatically assign ids to records that are
# inserted. (An upcoming feature will allow you to give an id "factory" # inserted. (An upcoming feature will allow you to give an id "factory"
# to a database and/or a collection.) # to a database and/or a collection.)
# #
# 12 bytes # 12 bytes
# --- # ---
# 0 time # 0 time
# 1 # 1
# 2 # 2
# 3 # 3
# 4 machine # 4 machine
# 5 # 5
# 6 # 6
# 7 pid # 7 pid
# 8 # 8
# 9 inc # 9 inc
# 10 # 10
# 11 # 11
class ObjectID class ObjectID
MACHINE = ( val = rand(0x1000000); [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff] ) MACHINE = ( val = rand(0x1000000); [val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff] )
PID = ( val = rand(0x10000); [val & 0xff, (val >> 8) & 0xff]; ) PID = ( val = rand(0x10000); [val & 0xff, (val >> 8) & 0xff]; )
# The string representation of an OID is different than its internal # The string representation of an OID is different than its internal
# and BSON byte representations. The BYTE_ORDER here maps # and BSON byte representations. The BYTE_ORDER here maps
# internal/BSON byte position (the index in BYTE_ORDER) to the # internal/BSON byte position (the index in BYTE_ORDER) to the
# position of the two hex characters representing that byte in the # position of the two hex characters representing that byte in the
# string representation. For example, the 0th BSON byte corresponds to # string representation. For example, the 0th BSON byte corresponds to
# the (0-based) 7th pair of hex chars in the string. # the (0-based) 7th pair of hex chars in the string.
BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8] BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8]
LOCK = Object.new LOCK = Object.new
LOCK.extend Mutex_m LOCK.extend Mutex_m
@@index_time = Time.new.to_i @@index_time = Time.new.to_i
@@index = 0 @@index = 0
# Given a string representation of an ObjectID, return a new ObjectID # Given a string representation of an ObjectID, return a new ObjectID
# with that value. # with that value.
def self.from_string(str) def self.from_string(str)
raise "illegal ObjectID format" unless legal?(str) raise "illegal ObjectID format" unless legal?(str)
data = [] data = []
BYTE_ORDER.each_with_index { |string_position, data_index| BYTE_ORDER.each_with_index { |string_position, data_index|
data[data_index] = str[string_position * 2, 2].to_i(16) data[data_index] = str[string_position * 2, 2].to_i(16)
} }
self.new(data) self.new(data)
end
def self.legal?(str)
len = BYTE_ORDER.length * 2
str =~ /([0-9a-f]+)/i
match = $1
str && str.length == len && match == str
end
# +data+ is an array of bytes. If nil, a new id will be generated.
# The time +t+ is only used for testing; leave it nil.
def initialize(data=nil, t=nil)
@data = data || generate_id(t)
end
def eql?(other)
@data == other.instance_variable_get("@data")
end
alias_method :==, :eql?
def to_a
@data.dup
end
def to_s
str = ' ' * 24
BYTE_ORDER.each_with_index { |string_position, data_index|
str[string_position * 2, 2] = '%02x' % @data[data_index]
}
str
end
# (Would normally be private, but isn't so we can test it.)
def generate_id(t=nil)
t ||= Time.new.to_i
buf = ByteBuffer.new
buf.put_int(t & 0xffffffff)
buf.put_array(MACHINE)
buf.put_array(PID)
i = index_for_time(t)
buf.put(i & 0xff)
buf.put((i >> 8) & 0xff)
buf.put((i >> 16) & 0xff)
buf.rewind
buf.to_a.dup
end
# (Would normally be private, but isn't so we can test it.)
def index_for_time(t)
LOCK.mu_synchronize {
if t != @@index_time
@@index = 0
@@index_time = t
end
retval = @@index
@@index += 1
retval
}
end
end
end end
def self.legal?(str)
len = BYTE_ORDER.length * 2
str =~ /([0-9a-f]+)/i
match = $1
str && str.length == len && match == str
end
# +data+ is an array of bytes. If nil, a new id will be generated.
# The time +t+ is only used for testing; leave it nil.
def initialize(data=nil, t=nil)
@data = data || generate_id(t)
end
def eql?(other)
@data == other.instance_variable_get("@data")
end
alias_method :==, :eql?
def to_a
@data.dup
end
def to_s
str = ' ' * 24
BYTE_ORDER.each_with_index { |string_position, data_index|
str[string_position * 2, 2] = '%02x' % @data[data_index]
}
str
end
# (Would normally be private, but isn't so we can test it.)
def generate_id(t=nil)
t ||= Time.new.to_i
buf = ByteBuffer.new
buf.put_int(t & 0xffffffff)
buf.put_array(MACHINE)
buf.put_array(PID)
i = index_for_time(t)
buf.put(i & 0xff)
buf.put((i >> 8) & 0xff)
buf.put((i >> 16) & 0xff)
buf.rewind
buf.to_a.dup
end
# (Would normally be private, but isn't so we can test it.)
def index_for_time(t)
LOCK.mu_synchronize {
if t != @@index_time
@@index = 0
@@index_time = t
end
retval = @@index
@@index += 1
retval
}
end
end end
end end

View File

@ -14,31 +14,27 @@
# limitations under the License. # limitations under the License.
# ++ # ++
module XGen module Mongo
module Mongo
module Driver
# A Regexp that can hold on to extra options and ignore them. Mongo # A Regexp that can hold on to extra options and ignore them. Mongo
# regexes may contain option characters beyond 'i', 'm', and 'x'. (Note # regexes may contain option characters beyond 'i', 'm', and 'x'. (Note
# that Mongo only uses those three, but that regexes coming from other # that Mongo only uses those three, but that regexes coming from other
# languages may store different option characters.) # languages may store different option characters.)
# #
# Note that you do not have to use this class at all if you wish to # Note that you do not have to use this class at all if you wish to
# store regular expressions in Mongo. The Mongo and Ruby regex option # store regular expressions in Mongo. The Mongo and Ruby regex option
# flags are the same. Storing regexes is discouraged, in any case. # flags are the same. Storing regexes is discouraged, in any case.
class RegexpOfHolding < Regexp class RegexpOfHolding < Regexp
attr_accessor :extra_options_str attr_accessor :extra_options_str
# +str+ and +options+ are the same as Regexp. +extra_options_str+
# contains all the other flags that were in Mongo but we do not use or
# understand.
def initialize(str, options, extra_options_str)
super(str, options)
@extra_options_str = extra_options_str
end
end
# +str+ and +options+ are the same as Regexp. +extra_options_str+
# contains all the other flags that were in Mongo but we do not use or
# understand.
def initialize(str, options, extra_options_str)
super(str, options)
@extra_options_str = extra_options_str
end end
end end
end end

View File

@ -14,19 +14,15 @@
# limitations under the License. # limitations under the License.
# ++ # ++
module XGen module Mongo
module Mongo
module Driver
# DEPRECATED - the ruby driver converts the BSON undefined type to nil, # DEPRECATED - the ruby driver converts the BSON undefined type to nil,
# and saves this type as nil # and saves this type as nil
class Undefined < Object class Undefined < Object
def initialize def initialize
super super
warn "the Undefined type is deprecated and will be removed - BSON undefineds get implicitely converted to nil now" warn "the Undefined type is deprecated and will be removed - BSON undefineds get implicitely converted to nil now"
end
end
end end
end end
end end

View File

@ -26,7 +26,7 @@ require 'mongo/types/undefined'
# A BSON seralizer/deserializer. # A BSON seralizer/deserializer.
class BSON class BSON
include XGen::Mongo::Driver include Mongo
MINKEY = -1 MINKEY = -1
EOO = 0 EOO = 0

View File

@ -21,7 +21,7 @@ require 'mongo'
# an OrderedHash. # an OrderedHash.
class XMLToRuby class XMLToRuby
include XGen::Mongo::Driver include Mongo
def xml_to_ruby(io) def xml_to_ruby(io)
doc = REXML::Document.new(io) doc = REXML::Document.new(io)

View File

@ -5,4 +5,4 @@ DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 27017 DEFAULT_PORT = 27017
DEFAULT_DB = 'driver_test_framework' DEFAULT_DB = 'driver_test_framework'
include XGen::Mongo::Driver include Mongo

View File

@ -3,9 +3,9 @@
require File.join(File.dirname(__FILE__), '_common.rb') require File.join(File.dirname(__FILE__), '_common.rb')
require 'mongo/gridfs' require 'mongo/gridfs'
include XGen::Mongo::GridFS include GridFS
db = Mongo.new(DEFAULT_HOST, DEFAULT_PORT).db(DEFAULT_DB) db = Mongo::Mongo.new(DEFAULT_HOST, DEFAULT_PORT).db(DEFAULT_DB)
input_file = ARGV[0] input_file = ARGV[0]

View File

@ -3,9 +3,9 @@
require File.join(File.dirname(__FILE__), '_common.rb') require File.join(File.dirname(__FILE__), '_common.rb')
require 'mongo/gridfs' require 'mongo/gridfs'
include XGen::Mongo::GridFS include GridFS
db = Mongo.new(DEFAULT_HOST, DEFAULT_PORT).db(DEFAULT_DB) db = Mongo::Mongo.new(DEFAULT_HOST, DEFAULT_PORT).db(DEFAULT_DB)
input_file = ARGV[0] input_file = ARGV[0]
output_file = ARGV[1] output_file = ARGV[1]

View File

@ -2,9 +2,9 @@
require File.join(File.dirname(__FILE__), '_common.rb') require File.join(File.dirname(__FILE__), '_common.rb')
include XGen::Mongo include Mongo
db = Mongo.new(DEFAULT_HOST, DEFAULT_PORT).db(DEFAULT_DB) db = Mongo::Mongo.new(DEFAULT_HOST, DEFAULT_PORT).db(DEFAULT_DB)
x = db.collection('x') x = db.collection('x')
y = db.collection('y') y = db.collection('y')

View File

@ -5,7 +5,7 @@ require 'test/unit'
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class AdminTest < Test::Unit::TestCase class AdminTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
@@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', @@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test') ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test')

View File

@ -5,7 +5,7 @@ require 'test/unit'
class BSONTest < Test::Unit::TestCase class BSONTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
def setup def setup
# We don't pass a DB to the constructor, even though we are about to test # We don't pass a DB to the constructor, even though we are about to test
@ -85,7 +85,7 @@ class BSONTest < Test::Unit::TestCase
assert_equal doc, doc2 assert_equal doc, doc2
r = doc2['doc'] r = doc2['doc']
assert_kind_of XGen::Mongo::Driver::RegexpOfHolding, r assert_kind_of RegexpOfHolding, r
assert_equal '', r.extra_options_str assert_equal '', r.extra_options_str
r.extra_options_str << 'zywcab' r.extra_options_str << 'zywcab'
@ -99,7 +99,7 @@ class BSONTest < Test::Unit::TestCase
assert_equal doc, doc2 assert_equal doc, doc2
r = doc2['doc'] r = doc2['doc']
assert_kind_of XGen::Mongo::Driver::RegexpOfHolding, r assert_kind_of RegexpOfHolding, r
assert_equal 'abcwyz', r.extra_options_str # must be sorted assert_equal 'abcwyz', r.extra_options_str # must be sorted
end end

View File

@ -5,8 +5,8 @@ require 'mongo/gridfs'
class ChunkTest < Test::Unit::TestCase class ChunkTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
include XGen::Mongo::GridFS include GridFS
@@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', @@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-utils-test') ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-utils-test')

View File

@ -20,8 +20,7 @@ require 'test/unit'
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class TestCollection < Test::Unit::TestCase class TestCollection < Test::Unit::TestCase
include XGen::Mongo include Mongo
include XGen::Mongo::Driver
@@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', @@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test') ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test')

View File

@ -5,7 +5,7 @@ require 'test/unit'
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class CursorTest < Test::Unit::TestCase class CursorTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
@@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', @@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test') ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test')

View File

@ -5,7 +5,7 @@ require 'test/unit'
class TestPKFactory class TestPKFactory
def create_pk(row) def create_pk(row)
row['_id'] ||= XGen::Mongo::Driver::ObjectID.new row['_id'] ||= Mongo::ObjectID.new
row row
end end
end end
@ -13,7 +13,7 @@ end
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class DBTest < Test::Unit::TestCase class DBTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
@@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' @@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
@@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT @@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT
@ -85,7 +85,7 @@ class DBTest < Test::Unit::TestCase
assert_not_nil oid assert_not_nil oid
assert_equal insert_id, oid assert_equal insert_id, oid
oid = XGen::Mongo::Driver::ObjectID.new oid = ObjectID.new
data = {'_id' => oid, 'name' => 'Barney', 'age' => 41} data = {'_id' => oid, 'name' => 'Barney', 'age' => 41}
coll.insert(data) coll.insert(data)
row = coll.find_one({'name' => data['name']}) row = coll.find_one({'name' => data['name']})

View File

@ -4,8 +4,7 @@ require 'test/unit'
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class DBAPITest < Test::Unit::TestCase class DBAPITest < Test::Unit::TestCase
include XGen::Mongo include Mongo
include XGen::Mongo::Driver
@@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', @@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test') ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test')
@ -817,18 +816,4 @@ class DBAPITest < Test::Unit::TestCase
@@db.collection("test").find({}, :snapshot => true, :sort => 'a').to_a @@db.collection("test").find({}, :snapshot => true, :sort => 'a').to_a
end end
end end
# TODO this test fails with error message "Undefed Before end of object"
# That is a database error. The undefined type may go away.
# def test_insert_undefined
# doc = {'undef' => Undefined.new}
# @@coll.clear
# @@coll.insert(doc)
# p @@db.error # DEBUG
# assert_equal 1, @@coll.count
# row = @@coll.find().next_object
# assert_not_nil row
# end
end end

View File

@ -5,7 +5,7 @@ require 'test/unit'
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class DBConnectionTest < Test::Unit::TestCase class DBConnectionTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
def test_no_exceptions def test_no_exceptions
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'

View File

@ -5,8 +5,8 @@ require 'mongo/gridfs'
class GridStoreTest < Test::Unit::TestCase class GridStoreTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
include XGen::Mongo::GridFS include GridFS
@@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', @@db = Mongo.new(ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost',
ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test') ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT).db('ruby-mongo-test')

View File

@ -4,7 +4,7 @@ require 'test/unit'
class MessageTest < Test::Unit::TestCase class MessageTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
def setup def setup
@msg = Message.new(42) @msg = Message.new(42)

View File

@ -5,7 +5,7 @@ require 'test/unit'
# NOTE: assumes Mongo is running # NOTE: assumes Mongo is running
class MongoTest < Test::Unit::TestCase class MongoTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
def setup def setup
@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' @host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'

View File

@ -4,7 +4,7 @@ require 'test/unit'
class ObjectIDTest < Test::Unit::TestCase class ObjectIDTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
def setup def setup
@t = 42 @t = 42

View File

@ -13,7 +13,7 @@ require 'test/unit'
# of this project), then we find the BSON test files there and use those, too. # of this project), then we find the BSON test files there and use those, too.
class RoundTripTest < Test::Unit::TestCase class RoundTripTest < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
@@ruby = nil @@ruby = nil

View File

@ -4,7 +4,7 @@ require 'test/unit'
class TestThreading < Test::Unit::TestCase class TestThreading < Test::Unit::TestCase
include XGen::Mongo::Driver include Mongo
@@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' @@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
@@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT @@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT