diff --git a/Rakefile b/Rakefile index c3ae028..e579e5f 100644 --- a/Rakefile +++ b/Rakefile @@ -27,6 +27,7 @@ namespace :test do ENV['C_EXT'] = 'TRUE' Rake::Task['test:unit'].invoke Rake::Task['test:functional'].invoke + Rake::Task['test:bson'].invoke Rake::Task['test:pooled_threading'].invoke Rake::Task['test:drop_databases'].invoke ENV['C_EXT'] = nil @@ -37,10 +38,11 @@ namespace :test do ENV['C_EXT'] = nil Rake::Task['test:unit'].invoke Rake::Task['test:functional'].invoke + Rake::Task['test:bson'].invoke Rake::Task['test:pooled_threading'].invoke Rake::Task['test:drop_databases'].invoke end - + Rake::TestTask.new(:unit) do |t| t.test_files = FileList['test/unit/*_test.rb'] t.verbose = true @@ -86,6 +88,11 @@ namespace :test do t.verbose = true end + Rake::TestTask.new(:bson) do |t| + t.test_files = FileList['test/mongo_bson/*_test.rb'] + t.verbose = true + end + task :drop_databases do |t| puts "Dropping test database..." require File.join(File.dirname(__FILE__), 'lib', 'mongo') diff --git a/bin/bson_benchmark.rb b/bin/bson_benchmark.rb index 8f7acbc..80563b9 100644 --- a/bin/bson_benchmark.rb +++ b/bin/bson_benchmark.rb @@ -9,9 +9,9 @@ TRIALS = 100000 def encode(doc) t0 = Time.new - b = BSON.new + b = Mongo::BSON_CODER.new TRIALS.times { |i| - b = BSON.new + b = Mongo::BSON_CODER.new b.serialize doc } print "took: #{Time.now.to_f - t0.to_f}\n" diff --git a/ext/cbson/cbson.c b/ext/cbson/cbson.c index 7af6512..03a171b 100644 --- a/ext/cbson/cbson.c +++ b/ext/cbson/cbson.c @@ -128,9 +128,22 @@ static void write_utf8(buffer_t buffer, VALUE string, char check_null) { #define INT2STRING(buffer, i) asprintf(buffer, "%d", i); #endif -// this sucks too. -#ifndef RREGEXP_SRC -#define RREGEXP_SRC(r) rb_str_new(RREGEXP((r))->str, RREGEXP((r))->len) +/* for rubinius compatibility, use the RREGEXP_SOURCE macro to retrieve + * the regex's source pattern. MRI 1.8 and 1.9 both have RREGEXP_SRC + * defined, but the underlying structure is different, so the second + * if/else takes care of that. + */ +#ifndef RREGEXP_SOURCE +#ifdef RREGEXP_SRC +#define RREGEXP_SOURCE(r) RREGEXP_SRC(r) +#else +#define RREGEXP_SOURCE(r) rb_str_new(RREGEXP((r))->str, RREGEXP((r))->len) +#endif +#endif + +// rubinius compatibility +#ifndef RREGEXP_OPTIONS +#define RREGEXP_OPTIONS(r) RREGEXP(value)->ptr->options #endif static char zero = 0; @@ -275,7 +288,7 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { } case T_STRING: { - if (strcmp(rb_class2name(RBASIC(value)->klass), + if (strcmp(rb_obj_classname(value), "Mongo::Code") == 0) { buffer_position length_location, start_position, total_length; int length; @@ -319,7 +332,7 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { case T_OBJECT: { // TODO there has to be a better way to do these checks... - const char* cls = rb_class2name(RBASIC(value)->klass); + const char* cls = rb_obj_classname(value); if (strcmp(cls, "Mongo::Binary") == 0 || strcmp(cls, "ByteBuffer") == 0) { const char subtype = strcmp(cls, "ByteBuffer") ? @@ -397,7 +410,7 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { } case T_DATA: { - const char* cls = rb_class2name(RBASIC(value)->klass); + const char* cls = rb_obj_classname(value); if (strcmp(cls, "Time") == 0) { double t = NUM2DBL(rb_funcall(value, rb_intern("to_f"), 0)); long long time_since_epoch = (long long)round(t * 1000); @@ -416,8 +429,8 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { } case T_REGEXP: { - VALUE pattern = RREGEXP_SRC(value); - long flags = RREGEXP(value)->ptr->options; + VALUE pattern = RREGEXP_SOURCE(value); + long flags = RREGEXP_OPTIONS(value); VALUE has_extra; write_name_and_type(buffer, key, 0x0B); @@ -452,7 +465,7 @@ static int write_element(VALUE key, VALUE value, VALUE extra, int allow_id) { } default: { - const char* cls = rb_class2name(RBASIC(value)->klass); + const char* cls = rb_obj_classname(value); buffer_free(buffer); rb_raise(InvalidDocument, "Cannot serialize an object of class %s (type %d) into BSON.", cls, TYPE(value)); break; @@ -495,7 +508,7 @@ static void write_doc(buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_ } else { allow_id = 1; - if (strcmp(rb_class2name(RBASIC(hash)->klass), "Hash") == 0) { + if (strcmp(rb_obj_classname(hash), "Hash") == 0) { if ((rb_funcall(hash, rb_intern("has_key?"), 1, id_str) == Qtrue) && (rb_funcall(hash, rb_intern("has_key?"), 1, id_sym) == Qtrue)) { VALUE oid_sym = rb_hash_delete(hash, id_sym); @@ -512,7 +525,7 @@ static void write_doc(buffer_t buffer, VALUE hash, VALUE check_keys, VALUE move_ } // we have to check for an OrderedHash and handle that specially - if (strcmp(rb_class2name(RBASIC(hash)->klass), "OrderedHash") == 0) { + if (strcmp(rb_obj_classname(hash), "OrderedHash") == 0) { VALUE keys = rb_funcall(hash, rb_intern("keys"), 0); int i; for(i = 0; i < RARRAY_LEN(keys); i++) { @@ -883,25 +896,25 @@ void Init_cbson() { Time = rb_const_get(rb_cObject, rb_intern("Time")); mongo = rb_const_get(rb_cObject, rb_intern("Mongo")); - rb_require("mongo/types/binary"); + rb_require("mongo_bson/types/binary"); Binary = rb_const_get(mongo, rb_intern("Binary")); - rb_require("mongo/types/objectid"); + rb_require("mongo_bson/types/objectid"); ObjectID = rb_const_get(mongo, rb_intern("ObjectID")); - rb_require("mongo/types/dbref"); + rb_require("mongo_bson/types/dbref"); DBRef = rb_const_get(mongo, rb_intern("DBRef")); - rb_require("mongo/types/code"); + rb_require("mongo_bson/types/code"); Code = rb_const_get(mongo, rb_intern("Code")); - rb_require("mongo/types/min_max_keys"); + rb_require("mongo_bson/types/min_max_keys"); MinKey = rb_const_get(mongo, rb_intern("MinKey")); MaxKey = rb_const_get(mongo, rb_intern("MaxKey")); - rb_require("mongo/types/regexp_of_holding"); + rb_require("mongo_bson/types/regexp_of_holding"); Regexp = rb_const_get(rb_cObject, rb_intern("Regexp")); RegexpOfHolding = rb_const_get(mongo, rb_intern("RegexpOfHolding")); - rb_require("mongo/exceptions"); + rb_require("mongo_bson/exceptions"); InvalidName = rb_const_get(mongo, rb_intern("InvalidName")); InvalidStringEncoding = rb_const_get(mongo, rb_intern("InvalidStringEncoding")); InvalidDocument = rb_const_get(mongo, rb_intern("InvalidDocument")); - rb_require("mongo/util/ordered_hash"); + rb_require("mongo_bson/ordered_hash"); OrderedHash = rb_const_get(rb_cObject, rb_intern("OrderedHash")); CBson = rb_define_module("CBson"); diff --git a/lib/mongo.rb b/lib/mongo.rb index 2320e8d..3258225 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -4,21 +4,8 @@ module Mongo VERSION = "0.19.2" end -begin - # Need this for running test with and without c ext in Ruby 1.9. - raise LoadError if ENV['TEST_MODE'] && !ENV['C_EXT'] - require 'mongo_ext/cbson' - raise LoadError unless defined?(CBson::VERSION) && CBson::VERSION == Mongo::VERSION - require 'mongo/util/bson_c' - BSON = BSON_C - rescue LoadError - require 'mongo/util/bson_ruby' - BSON = BSON_RUBY - warn "\n**Notice: C extension not loaded. This is required for optimum MongoDB Ruby driver performance." - warn " You can install the extension as follows:\n gem install mongo_ext\n" - warn " If you continue to receive this message after installing, make sure that the" - warn " mongo_ext gem is in your load path and that the mongo_ext and mongo gems are of the same version.\n" -end +require 'mongo_bson' + module Mongo ASCENDING = 1 @@ -40,17 +27,9 @@ module Mongo end -require 'mongo/types/binary' -require 'mongo/types/code' -require 'mongo/types/dbref' -require 'mongo/types/objectid' -require 'mongo/types/regexp_of_holding' -require 'mongo/types/min_max_keys' - require 'mongo/util/support' require 'mongo/util/conversions' require 'mongo/util/server_version' -require 'mongo/util/bson_ruby' require 'mongo/collection' require 'mongo/connection' diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index de0c106..a832657 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -261,7 +261,7 @@ module Mongo message = ByteBuffer.new([0, 0, 0, 0]) BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}") message.put_int(0) - message.put_array(BSON.serialize(selector, false, true).to_a) + message.put_array(BSON_CODER.serialize(selector, false, true).to_a) if opts[:safe] @connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, @@ -303,8 +303,8 @@ module Mongo update_options += 1 if options[:upsert] update_options += 2 if options[:multi] message.put_int(update_options) - message.put_array(BSON.serialize(selector, false, true).to_a) - message.put_array(BSON.serialize(document, false, true).to_a) + message.put_array(BSON_CODER.serialize(selector, false, true).to_a) + message.put_array(BSON_CODER.serialize(document, false, true).to_a) if options[:safe] @connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, "db.#{@name}.update(#{selector.inspect}, #{document.inspect})") @@ -590,7 +590,7 @@ module Mongo # Initial byte is 0. message = ByteBuffer.new([0, 0, 0, 0]) BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{collection_name}") - documents.each { |doc| message.put_array(BSON.serialize(doc, check_keys, true).to_a) } + documents.each { |doc| message.put_array(BSON_CODER.serialize(doc, check_keys, true).to_a) } if safe @connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name, "db.#{collection_name}.insert(#{documents.inspect})") diff --git a/lib/mongo/connection.rb b/lib/mongo/connection.rb index 336cd1b..36247dc 100644 --- a/lib/mongo/connection.rb +++ b/lib/mongo/connection.rb @@ -650,7 +650,7 @@ module Mongo buf.put_array(receive_message_on_socket(size - 4, sock).unpack("C*"), 4) number_remaining -= 1 buf.rewind - docs << BSON.deserialize(buf) + docs << BSON_CODER.deserialize(buf) end [docs, number_received, cursor_id] end @@ -661,7 +661,7 @@ module Mongo BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd") message.put_int(0) message.put_int(-1) - message.put_array(BSON.serialize({:getlasterror => 1}, false).unpack("C*")) + message.put_array(BSON_CODER.serialize({:getlasterror => 1}, false).unpack("C*")) add_message_headers(Mongo::Constants::OP_QUERY, message) end diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 6af0add..4a42921 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -356,8 +356,8 @@ module Mongo if query_contains_special_fields? selector = selector_with_special_query_fields end - message.put_array(BSON.serialize(selector, false).to_a) - message.put_array(BSON.serialize(@fields, false).to_a) if @fields + message.put_array(BSON_CODER.serialize(selector, false).to_a) + message.put_array(BSON_CODER.serialize(@fields, false).to_a) if @fields message end diff --git a/lib/mongo/gridfs/chunk.rb b/lib/mongo/gridfs/chunk.rb index 208764d..246b0d1 100644 --- a/lib/mongo/gridfs/chunk.rb +++ b/lib/mongo/gridfs/chunk.rb @@ -14,9 +14,9 @@ # limitations under the License. # ++ -require 'mongo/types/objectid' -require 'mongo/util/byte_buffer' -require 'mongo/util/ordered_hash' +require 'mongo_bson/types/objectid' +require 'mongo_bson/byte_buffer' +require 'mongo_bson/ordered_hash' module GridFS diff --git a/lib/mongo/gridfs/grid_store.rb b/lib/mongo/gridfs/grid_store.rb index fa9730f..e4ff276 100644 --- a/lib/mongo/gridfs/grid_store.rb +++ b/lib/mongo/gridfs/grid_store.rb @@ -14,8 +14,8 @@ # limitations under the License. # ++ -require 'mongo/types/objectid' -require 'mongo/util/ordered_hash' +require 'mongo_bson/types/objectid' +require 'mongo_bson/ordered_hash' require 'mongo/gridfs/chunk' module GridFS diff --git a/lib/mongo_bson/bson_c.rb b/lib/mongo_bson/bson_c.rb index b220b35..dcdddec 100644 --- a/lib/mongo_bson/bson_c.rb +++ b/lib/mongo_bson/bson_c.rb @@ -1,18 +1,20 @@ # A thin wrapper for the CBson class -class BSON_C +module Mongo + class BSON_C - def self.serialize(obj, check_keys=false, move_id=false) - ByteBuffer.new(CBson.serialize(obj, check_keys, move_id)) - end - - def self.deserialize(buf=nil) - if buf.is_a? String - to_deserialize = ByteBuffer.new(buf) if buf - else - buf = ByteBuffer.new(buf.to_a) if buf + def self.serialize(obj, check_keys=false, move_id=false) + ByteBuffer.new(CBson.serialize(obj, check_keys, move_id)) end - buf.rewind - CBson.deserialize(buf.to_s) - end + def self.deserialize(buf=nil) + if buf.is_a? String + to_deserialize = ByteBuffer.new(buf) if buf + else + buf = ByteBuffer.new(buf.to_a) if buf + end + buf.rewind + CBson.deserialize(buf.to_s) + end + + end end diff --git a/lib/mongo_bson/bson_ruby.rb b/lib/mongo_bson/bson_ruby.rb index 3a2dbf7..945424a 100644 --- a/lib/mongo_bson/bson_ruby.rb +++ b/lib/mongo_bson/bson_ruby.rb @@ -14,593 +14,588 @@ # along with this program. If not, see . # ++ -require 'base64' -require 'mongo/util/byte_buffer' -require 'mongo/util/ordered_hash' -require 'mongo/types/binary' -require 'mongo/types/dbref' -require 'mongo/types/objectid' -require 'mongo/types/regexp_of_holding' +module Mongo + # A BSON seralizer/deserializer in pure Ruby. + class BSON_RUBY -# A BSON seralizer/deserializer in pure Ruby. -class BSON_RUBY + # why was this necessary? + #include Mongo - include Mongo + MINKEY = -1 + EOO = 0 + NUMBER = 1 + STRING = 2 + OBJECT = 3 + ARRAY = 4 + BINARY = 5 + UNDEFINED = 6 + OID = 7 + BOOLEAN = 8 + DATE = 9 + NULL = 10 + REGEX = 11 + REF = 12 + CODE = 13 + SYMBOL = 14 + CODE_W_SCOPE = 15 + NUMBER_INT = 16 + TIMESTAMP = 17 + NUMBER_LONG = 18 + MAXKEY = 127 - MINKEY = -1 - EOO = 0 - NUMBER = 1 - STRING = 2 - OBJECT = 3 - ARRAY = 4 - BINARY = 5 - UNDEFINED = 6 - OID = 7 - BOOLEAN = 8 - DATE = 9 - NULL = 10 - REGEX = 11 - REF = 12 - CODE = 13 - SYMBOL = 14 - CODE_W_SCOPE = 15 - NUMBER_INT = 16 - TIMESTAMP = 17 - NUMBER_LONG = 18 - MAXKEY = 127 - - def initialize - @buf = ByteBuffer.new - end - - if RUBY_VERSION >= '1.9' - def self.to_utf8(str) - str.encode("utf-8") + def initialize + @buf = ByteBuffer.new end - else - def self.to_utf8(str) - begin - str.unpack("U*") - rescue => ex - raise InvalidStringEncoding, "String not valid utf-8: #{str}" + + if RUBY_VERSION >= '1.9' + def self.to_utf8(str) + str.encode("utf-8") + end + else + def self.to_utf8(str) + begin + str.unpack("U*") + rescue => ex + raise InvalidStringEncoding, "String not valid utf-8: #{str}" + end + str + end + end + + def self.serialize_cstr(buf, val) + buf.put_array(to_utf8(val.to_s).unpack("C*") << 0) + end + + def self.serialize_key(buf, key) + raise InvalidDocument, "Key names / regex patterns must not contain the NULL byte" if key.include? "\x00" + self.serialize_cstr(buf, key) + end + + def to_a + @buf.to_a + end + + def to_s + @buf.to_s + end + + # Serializes an object. + # Implemented to ensure an API compatible with BSON extension. + def self.serialize(obj, check_keys=false, move_id=false) + new.serialize(obj, check_keys, move_id) + end + + def self.deserialize(buf=nil) + new.deserialize(buf) + end + + def serialize(obj, check_keys=false, move_id=false) + raise "Document is null" unless obj + + @buf.rewind + # put in a placeholder for the total size + @buf.put_int(0) + + # Write key/value pairs. Always write _id first if it exists. + if move_id + if obj.has_key? '_id' + serialize_key_value('_id', obj['_id'], false) + elsif obj.has_key? :_id + serialize_key_value('_id', obj[:_id], false) + end + obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id } + else + if obj.has_key?('_id') && obj.has_key?(:_id) + obj['_id'] = obj.delete(:_id) + end + obj.each {|k, v| serialize_key_value(k, v, check_keys) } + end + + serialize_eoo_element(@buf) + if @buf.size > 4 * 1024 * 1024 + raise InvalidDocument, "Document is too large (#{@buf.size}). BSON documents are limited to 4MB (#{4 * 1024 * 1024})." + end + @buf.put_int(@buf.size, 0) + self + end + + # Returns the array stored in the buffer. + # Implemented to ensure an API compatible with BSON extension. + def unpack(arg) + @buf.to_a + end + + def serialize_key_value(k, v, check_keys) + k = k.to_s + if check_keys + if k[0] == ?$ + raise InvalidName.new("key #{k} must not start with '$'") + end + if k.include? ?. + raise InvalidName.new("key #{k} must not contain '.'") + end + end + type = bson_type(v) + case type + when STRING, SYMBOL + serialize_string_element(@buf, k, v, type) + when NUMBER, NUMBER_INT + serialize_number_element(@buf, k, v, type) + when OBJECT + serialize_object_element(@buf, k, v, check_keys) + when OID + serialize_oid_element(@buf, k, v) + when ARRAY + serialize_array_element(@buf, k, v, check_keys) + when REGEX + serialize_regex_element(@buf, k, v) + when BOOLEAN + serialize_boolean_element(@buf, k, v) + when DATE + serialize_date_element(@buf, k, v) + when NULL + serialize_null_element(@buf, k) + when REF + serialize_dbref_element(@buf, k, v) + when BINARY + serialize_binary_element(@buf, k, v) + when UNDEFINED + serialize_null_element(@buf, k) + when CODE_W_SCOPE + serialize_code_w_scope(@buf, k, v) + when MAXKEY + serialize_max_key_element(@buf, k) + when MINKEY + serialize_min_key_element(@buf, k) + else + raise "unhandled type #{type}" + end + end + + def deserialize(buf=nil) + # If buf is nil, use @buf, assumed to contain already-serialized BSON. + # This is only true during testing. + if buf.is_a? String + @buf = ByteBuffer.new(buf) if buf + else + @buf = ByteBuffer.new(buf.to_a) if buf + end + @buf.rewind + @buf.get_int # eat message size + doc = OrderedHash.new + while @buf.more? + type = @buf.get + case type + when STRING, CODE + key = deserialize_cstr(@buf) + doc[key] = deserialize_string_data(@buf) + when SYMBOL + key = deserialize_cstr(@buf) + doc[key] = deserialize_string_data(@buf).intern + when NUMBER + key = deserialize_cstr(@buf) + doc[key] = deserialize_number_data(@buf) + when NUMBER_INT + key = deserialize_cstr(@buf) + doc[key] = deserialize_number_int_data(@buf) + when NUMBER_LONG + key = deserialize_cstr(@buf) + doc[key] = deserialize_number_long_data(@buf) + when OID + key = deserialize_cstr(@buf) + doc[key] = deserialize_oid_data(@buf) + when ARRAY + key = deserialize_cstr(@buf) + doc[key] = deserialize_array_data(@buf) + when REGEX + key = deserialize_cstr(@buf) + doc[key] = deserialize_regex_data(@buf) + when OBJECT + key = deserialize_cstr(@buf) + doc[key] = deserialize_object_data(@buf) + when BOOLEAN + key = deserialize_cstr(@buf) + doc[key] = deserialize_boolean_data(@buf) + when DATE + key = deserialize_cstr(@buf) + doc[key] = deserialize_date_data(@buf) + when NULL + key = deserialize_cstr(@buf) + doc[key] = nil + when UNDEFINED + key = deserialize_cstr(@buf) + doc[key] = nil + when REF + key = deserialize_cstr(@buf) + doc[key] = deserialize_dbref_data(@buf) + when BINARY + key = deserialize_cstr(@buf) + doc[key] = deserialize_binary_data(@buf) + when CODE_W_SCOPE + key = deserialize_cstr(@buf) + doc[key] = deserialize_code_w_scope_data(@buf) + when TIMESTAMP + key = deserialize_cstr(@buf) + doc[key] = [deserialize_number_int_data(@buf), + deserialize_number_int_data(@buf)] + when MAXKEY + key = deserialize_cstr(@buf) + doc[key] = MaxKey.new + when MINKEY, 255 # This is currently easier than unpack the type byte as an unsigned char. + key = deserialize_cstr(@buf) + doc[key] = MinKey.new + when EOO + break + else + raise "Unknown type #{type}, key = #{key}" + end + end + @buf.rewind + doc + end + + # For debugging. + def hex_dump + str = '' + @buf.to_a.each_with_index { |b,i| + if (i % 8) == 0 + str << "\n" if i > 0 + str << '%4d: ' % i + else + str << ' ' + end + str << '%02X' % b + } + str + end + + def deserialize_date_data(buf) + unsigned = buf.get_long() + # see note for deserialize_number_long_data below + milliseconds = unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned + Time.at(milliseconds.to_f / 1000.0).utc # at() takes fractional seconds + end + + def deserialize_boolean_data(buf) + buf.get == 1 + end + + def deserialize_number_data(buf) + buf.get_double + end + + def deserialize_number_int_data(buf) + # sometimes ruby makes me angry... why would the same code pack as signed + # but unpack as unsigned + unsigned = buf.get_int + unsigned >= 2**32 / 2 ? unsigned - 2**32 : unsigned + end + + def deserialize_number_long_data(buf) + # same note as above applies here... + unsigned = buf.get_long + unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned + end + + def deserialize_object_data(buf) + size = buf.get_int + buf.position -= 4 + object = BSON_CODER.new().deserialize(buf.get(size)) + if object.has_key? "$ref" + DBRef.new(object["$ref"], object["$id"]) + else + object + end + end + + def deserialize_array_data(buf) + h = deserialize_object_data(buf) + a = [] + h.each { |k, v| a[k.to_i] = v } + a + end + + def deserialize_regex_data(buf) + str = deserialize_cstr(buf) + options_str = deserialize_cstr(buf) + options = 0 + options |= Regexp::IGNORECASE if options_str.include?('i') + options |= Regexp::MULTILINE if options_str.include?('m') + options |= Regexp::EXTENDED if options_str.include?('x') + options_str.gsub!(/[imx]/, '') # Now remove the three we understand + if options_str == '' + Regexp.new(str, options) + else + warn("Using deprecated Regexp options #{options_str}; future versions of this MongoDB driver will support only i, m, and x. See deprecated class RegexpOfHolding for more info.") + RegexpOfHolding.new(str, options, options_str) + end + end + + def deserialize_string_data(buf) + len = buf.get_int + bytes = buf.get(len) + str = bytes[0..-2] + if str.respond_to? "pack" + str = str.pack("C*") + end + if RUBY_VERSION >= '1.9' + str.force_encoding("utf-8") end str end - end - def self.serialize_cstr(buf, val) - buf.put_array(to_utf8(val.to_s).unpack("C*") << 0) - end - - def self.serialize_key(buf, key) - raise InvalidDocument, "Key names / regex patterns must not contain the NULL byte" if key.include? "\x00" - self.serialize_cstr(buf, key) - end - - def to_a - @buf.to_a - end - - def to_s - @buf.to_s - end - - # Serializes an object. - # Implemented to ensure an API compatible with BSON extension. - def self.serialize(obj, check_keys=false, move_id=false) - new.serialize(obj, check_keys, move_id) - end - - def self.deserialize(buf=nil) - new.deserialize(buf) - end - - def serialize(obj, check_keys=false, move_id=false) - raise "Document is null" unless obj - - @buf.rewind - # put in a placeholder for the total size - @buf.put_int(0) - - # Write key/value pairs. Always write _id first if it exists. - if move_id - if obj.has_key? '_id' - serialize_key_value('_id', obj['_id'], false) - elsif obj.has_key? :_id - serialize_key_value('_id', obj[:_id], false) + def deserialize_code_w_scope_data(buf) + buf.get_int + len = buf.get_int + code = buf.get(len)[0..-2] + if code.respond_to? "pack" + code = code.pack("C*") end - obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id } - else - if obj.has_key?('_id') && obj.has_key?(:_id) - obj['_id'] = obj.delete(:_id) + if RUBY_VERSION >= '1.9' + code.force_encoding("utf-8") end - obj.each {|k, v| serialize_key_value(k, v, check_keys) } + + scope_size = buf.get_int + buf.position -= 4 + scope = BSON_CODER.new().deserialize(buf.get(scope_size)) + + Code.new(code, scope) end - serialize_eoo_element(@buf) - if @buf.size > 4 * 1024 * 1024 - raise InvalidDocument, "Document is too large (#{@buf.size}). BSON documents are limited to 4MB (#{4 * 1024 * 1024})." - end - @buf.put_int(@buf.size, 0) - self - end - - # Returns the array stored in the buffer. - # Implemented to ensure an API compatible with BSON extension. - def unpack(arg) - @buf.to_a - end - - def serialize_key_value(k, v, check_keys) - k = k.to_s - if check_keys - if k[0] == ?$ - raise InvalidName.new("key #{k} must not start with '$'") - end - if k.include? ?. - raise InvalidName.new("key #{k} must not contain '.'") - end - end - type = bson_type(v) - case type - when STRING, SYMBOL - serialize_string_element(@buf, k, v, type) - when NUMBER, NUMBER_INT - serialize_number_element(@buf, k, v, type) - when OBJECT - serialize_object_element(@buf, k, v, check_keys) - when OID - serialize_oid_element(@buf, k, v) - when ARRAY - serialize_array_element(@buf, k, v, check_keys) - when REGEX - serialize_regex_element(@buf, k, v) - when BOOLEAN - serialize_boolean_element(@buf, k, v) - when DATE - serialize_date_element(@buf, k, v) - when NULL - serialize_null_element(@buf, k) - when REF - serialize_dbref_element(@buf, k, v) - when BINARY - serialize_binary_element(@buf, k, v) - when UNDEFINED - serialize_null_element(@buf, k) - when CODE_W_SCOPE - serialize_code_w_scope(@buf, k, v) - when MAXKEY - serialize_max_key_element(@buf, k) - when MINKEY - serialize_min_key_element(@buf, k) - else - raise "unhandled type #{type}" - end - end - - def deserialize(buf=nil) - # If buf is nil, use @buf, assumed to contain already-serialized BSON. - # This is only true during testing. - if buf.is_a? String - @buf = ByteBuffer.new(buf) if buf - else - @buf = ByteBuffer.new(buf.to_a) if buf - end - @buf.rewind - @buf.get_int # eat message size - doc = OrderedHash.new - while @buf.more? - type = @buf.get - case type - when STRING, CODE - key = deserialize_cstr(@buf) - doc[key] = deserialize_string_data(@buf) - when SYMBOL - key = deserialize_cstr(@buf) - doc[key] = deserialize_string_data(@buf).intern - when NUMBER - key = deserialize_cstr(@buf) - doc[key] = deserialize_number_data(@buf) - when NUMBER_INT - key = deserialize_cstr(@buf) - doc[key] = deserialize_number_int_data(@buf) - when NUMBER_LONG - key = deserialize_cstr(@buf) - doc[key] = deserialize_number_long_data(@buf) - when OID - key = deserialize_cstr(@buf) - doc[key] = deserialize_oid_data(@buf) - when ARRAY - key = deserialize_cstr(@buf) - doc[key] = deserialize_array_data(@buf) - when REGEX - key = deserialize_cstr(@buf) - doc[key] = deserialize_regex_data(@buf) - when OBJECT - key = deserialize_cstr(@buf) - doc[key] = deserialize_object_data(@buf) - when BOOLEAN - key = deserialize_cstr(@buf) - doc[key] = deserialize_boolean_data(@buf) - when DATE - key = deserialize_cstr(@buf) - doc[key] = deserialize_date_data(@buf) - when NULL - key = deserialize_cstr(@buf) - doc[key] = nil - when UNDEFINED - key = deserialize_cstr(@buf) - doc[key] = nil - when REF - key = deserialize_cstr(@buf) - doc[key] = deserialize_dbref_data(@buf) - when BINARY - key = deserialize_cstr(@buf) - doc[key] = deserialize_binary_data(@buf) - when CODE_W_SCOPE - key = deserialize_cstr(@buf) - doc[key] = deserialize_code_w_scope_data(@buf) - when TIMESTAMP - key = deserialize_cstr(@buf) - doc[key] = [deserialize_number_int_data(@buf), - deserialize_number_int_data(@buf)] - when MAXKEY - key = deserialize_cstr(@buf) - doc[key] = MaxKey.new - when MINKEY, 255 # This is currently easier than unpack the type byte as an unsigned char. - key = deserialize_cstr(@buf) - doc[key] = MinKey.new - when EOO - break - else - raise "Unknown type #{type}, key = #{key}" - end - end - @buf.rewind - doc - end - - # For debugging. - def hex_dump - str = '' - @buf.to_a.each_with_index { |b,i| - if (i % 8) == 0 - str << "\n" if i > 0 - str << '%4d: ' % i - else - str << ' ' - end - str << '%02X' % b - } - str - end - - def deserialize_date_data(buf) - unsigned = buf.get_long() - # see note for deserialize_number_long_data below - milliseconds = unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned - Time.at(milliseconds.to_f / 1000.0).utc # at() takes fractional seconds - end - - def deserialize_boolean_data(buf) - buf.get == 1 - end - - def deserialize_number_data(buf) - buf.get_double - end - - def deserialize_number_int_data(buf) - # sometimes ruby makes me angry... why would the same code pack as signed - # but unpack as unsigned - unsigned = buf.get_int - unsigned >= 2**32 / 2 ? unsigned - 2**32 : unsigned - end - - def deserialize_number_long_data(buf) - # same note as above applies here... - unsigned = buf.get_long - unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned - end - - def deserialize_object_data(buf) - size = buf.get_int - buf.position -= 4 - object = BSON.new().deserialize(buf.get(size)) - if object.has_key? "$ref" - DBRef.new(object["$ref"], object["$id"]) - else - object - end - end - - def deserialize_array_data(buf) - h = deserialize_object_data(buf) - a = [] - h.each { |k, v| a[k.to_i] = v } - a - end - - def deserialize_regex_data(buf) - str = deserialize_cstr(buf) - options_str = deserialize_cstr(buf) - options = 0 - options |= Regexp::IGNORECASE if options_str.include?('i') - options |= Regexp::MULTILINE if options_str.include?('m') - options |= Regexp::EXTENDED if options_str.include?('x') - options_str.gsub!(/[imx]/, '') # Now remove the three we understand - if options_str == '' - Regexp.new(str, options) - else - warn("Using deprecated Regexp options #{options_str}; future versions of this MongoDB driver will support only i, m, and x. See deprecated class RegexpOfHolding for more info.") - RegexpOfHolding.new(str, options, options_str) - end - end - - def deserialize_string_data(buf) - len = buf.get_int - bytes = buf.get(len) - str = bytes[0..-2] - if str.respond_to? "pack" - str = str.pack("C*") - end - if RUBY_VERSION >= '1.9' - str.force_encoding("utf-8") - end - str - end - - def deserialize_code_w_scope_data(buf) - buf.get_int - len = buf.get_int - code = buf.get(len)[0..-2] - if code.respond_to? "pack" - code = code.pack("C*") - end - if RUBY_VERSION >= '1.9' - code.force_encoding("utf-8") + def deserialize_oid_data(buf) + ObjectID.new(buf.get(12)) end - scope_size = buf.get_int - buf.position -= 4 - scope = BSON.new().deserialize(buf.get(scope_size)) - - Code.new(code, scope) - end - - def deserialize_oid_data(buf) - ObjectID.new(buf.get(12)) - end - - def deserialize_dbref_data(buf) - ns = deserialize_string_data(buf) - oid = deserialize_oid_data(buf) - DBRef.new(ns, oid) - end - - def deserialize_binary_data(buf) - len = buf.get_int - type = buf.get - len = buf.get_int if type == Binary::SUBTYPE_BYTES - Binary.new(buf.get(len), type) - end - - def serialize_eoo_element(buf) - buf.put(EOO) - end - - def serialize_null_element(buf, key) - buf.put(NULL) - self.class.serialize_key(buf, key) - end - - def serialize_dbref_element(buf, key, val) - oh = OrderedHash.new - oh['$ref'] = val.namespace - oh['$id'] = val.object_id - serialize_object_element(buf, key, oh, false) - end - - def serialize_binary_element(buf, key, val) - buf.put(BINARY) - self.class.serialize_key(buf, key) - - bytes = val.to_a - num_bytes = bytes.length - subtype = val.respond_to?(:subtype) ? val.subtype : Binary::SUBTYPE_BYTES - if subtype == Binary::SUBTYPE_BYTES - buf.put_int(num_bytes + 4) - buf.put(subtype) - buf.put_int(num_bytes) - buf.put_array(bytes) - else - buf.put_int(num_bytes) - buf.put(subtype) - buf.put_array(bytes) + def deserialize_dbref_data(buf) + ns = deserialize_string_data(buf) + oid = deserialize_oid_data(buf) + DBRef.new(ns, oid) end - end - def serialize_boolean_element(buf, key, val) - buf.put(BOOLEAN) - self.class.serialize_key(buf, key) - buf.put(val ? 1 : 0) - end + def deserialize_binary_data(buf) + len = buf.get_int + type = buf.get + len = buf.get_int if type == Binary::SUBTYPE_BYTES + Binary.new(buf.get(len), type) + end - def serialize_date_element(buf, key, val) - buf.put(DATE) - self.class.serialize_key(buf, key) - millisecs = (val.to_f * 1000).to_i - buf.put_long(millisecs) - end + def serialize_eoo_element(buf) + buf.put(EOO) + end - def serialize_number_element(buf, key, val, type) - if type == NUMBER - buf.put(type) + def serialize_null_element(buf, key) + buf.put(NULL) self.class.serialize_key(buf, key) - buf.put_double(val) - else - if val > 2**64 / 2 - 1 or val < -2**64 / 2 - raise RangeError.new("MongoDB can only handle 8-byte ints") - end - if val > 2**32 / 2 - 1 or val < -2**32 / 2 - buf.put(NUMBER_LONG) - self.class.serialize_key(buf, key) - buf.put_long(val) + end + + def serialize_dbref_element(buf, key, val) + oh = OrderedHash.new + oh['$ref'] = val.namespace + oh['$id'] = val.object_id + serialize_object_element(buf, key, oh, false) + end + + def serialize_binary_element(buf, key, val) + buf.put(BINARY) + self.class.serialize_key(buf, key) + + bytes = val.to_a + num_bytes = bytes.length + subtype = val.respond_to?(:subtype) ? val.subtype : Binary::SUBTYPE_BYTES + if subtype == Binary::SUBTYPE_BYTES + buf.put_int(num_bytes + 4) + buf.put(subtype) + buf.put_int(num_bytes) + buf.put_array(bytes) else + buf.put_int(num_bytes) + buf.put(subtype) + buf.put_array(bytes) + end + end + + def serialize_boolean_element(buf, key, val) + buf.put(BOOLEAN) + self.class.serialize_key(buf, key) + buf.put(val ? 1 : 0) + end + + def serialize_date_element(buf, key, val) + buf.put(DATE) + self.class.serialize_key(buf, key) + millisecs = (val.to_f * 1000).to_i + buf.put_long(millisecs) + end + + def serialize_number_element(buf, key, val, type) + if type == NUMBER buf.put(type) self.class.serialize_key(buf, key) - buf.put_int(val) - end - end - end - - def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT) - buf.put(opcode) - self.class.serialize_key(buf, key) - buf.put_array(BSON.new.serialize(val, check_keys).to_a) - end - - def serialize_array_element(buf, key, val, check_keys) - # Turn array into hash with integer indices as keys - h = OrderedHash.new - i = 0 - val.each { |v| h[i] = v; i += 1 } - serialize_object_element(buf, key, h, check_keys, ARRAY) - end - - def serialize_regex_element(buf, key, val) - buf.put(REGEX) - self.class.serialize_key(buf, key) - - str = val.source - # We use serialize_key here since regex patterns aren't prefixed with - # length (can't contain the NULL byte). - self.class.serialize_key(buf, str) - - options = val.options - options_str = '' - options_str << 'i' if ((options & Regexp::IGNORECASE) != 0) - options_str << 'm' if ((options & Regexp::MULTILINE) != 0) - options_str << 'x' if ((options & Regexp::EXTENDED) != 0) - options_str << val.extra_options_str if val.respond_to?(:extra_options_str) - # Must store option chars in alphabetical order - self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join) - end - - def serialize_max_key_element(buf, key) - buf.put(MAXKEY) - self.class.serialize_key(buf, key) - end - - def serialize_min_key_element(buf, key) - buf.put(MINKEY) - self.class.serialize_key(buf, key) - end - - def serialize_oid_element(buf, key, val) - buf.put(OID) - self.class.serialize_key(buf, key) - - buf.put_array(val.to_a) - end - - def serialize_string_element(buf, key, val, type) - buf.put(type) - self.class.serialize_key(buf, key) - - # Make a hole for the length - len_pos = buf.position - buf.put_int(0) - - # Save the string - start_pos = buf.position - self.class.serialize_cstr(buf, val) - end_pos = buf.position - - # Put the string size in front - buf.put_int(end_pos - start_pos, len_pos) - - # Go back to where we were - buf.position = end_pos - end - - def serialize_code_w_scope(buf, key, val) - buf.put(CODE_W_SCOPE) - self.class.serialize_key(buf, key) - - # Make a hole for the length - len_pos = buf.position - buf.put_int(0) - - buf.put_int(val.length + 1) - self.class.serialize_cstr(buf, val) - buf.put_array(BSON.new.serialize(val.scope).to_a) - - end_pos = buf.position - buf.put_int(end_pos - len_pos, len_pos) - buf.position = end_pos - end - - def deserialize_cstr(buf) - chars = "" - while true - b = buf.get - break if b == 0 - chars << b.chr - end - if RUBY_VERSION >= '1.9' - chars.force_encoding("utf-8") # Mongo stores UTF-8 - end - chars - end - - def bson_type(o) - case o - when nil - NULL - when Integer - NUMBER_INT - when Float - NUMBER - when ByteBuffer - BINARY - when Code - CODE_W_SCOPE - when String - STRING - when Array - ARRAY - when Regexp - REGEX - when ObjectID - OID - when DBRef - REF - when true, false - BOOLEAN - when Time - DATE - when Hash - OBJECT - when Symbol - SYMBOL - when MaxKey - MAXKEY - when MinKey - MINKEY - when Numeric - raise InvalidDocument, "Cannot serialize the Numeric type #{o.class} as BSON; only Fixum, Bignum, and Float are supported." - when Date, DateTime - raise InvalidDocument, "#{o.class} is not currently supported; " + - "use a UTC Time instance instead." - else - if defined?(ActiveSupport::TimeWithZone) && o.is_a?(ActiveSupport::TimeWithZone) - raise InvalidDocument, "ActiveSupport::TimeWithZone is not currently supported; " + - "use a UTC Time instance instead." + buf.put_double(val) else - raise InvalidDocument, "Cannot serialize #{o.class} as a BSON type; it either isn't supported or won't translate to BSON." + if val > 2**64 / 2 - 1 or val < -2**64 / 2 + raise RangeError.new("MongoDB can only handle 8-byte ints") + end + if val > 2**32 / 2 - 1 or val < -2**32 / 2 + buf.put(NUMBER_LONG) + self.class.serialize_key(buf, key) + buf.put_long(val) + else + buf.put(type) + self.class.serialize_key(buf, key) + buf.put_int(val) + end end end - end + def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT) + buf.put(opcode) + self.class.serialize_key(buf, key) + buf.put_array(BSON_CODER.new.serialize(val, check_keys).to_a) + end + + def serialize_array_element(buf, key, val, check_keys) + # Turn array into hash with integer indices as keys + h = OrderedHash.new + i = 0 + val.each { |v| h[i] = v; i += 1 } + serialize_object_element(buf, key, h, check_keys, ARRAY) + end + + def serialize_regex_element(buf, key, val) + buf.put(REGEX) + self.class.serialize_key(buf, key) + + str = val.source + # We use serialize_key here since regex patterns aren't prefixed with + # length (can't contain the NULL byte). + self.class.serialize_key(buf, str) + + options = val.options + options_str = '' + options_str << 'i' if ((options & Regexp::IGNORECASE) != 0) + options_str << 'm' if ((options & Regexp::MULTILINE) != 0) + options_str << 'x' if ((options & Regexp::EXTENDED) != 0) + options_str << val.extra_options_str if val.respond_to?(:extra_options_str) + # Must store option chars in alphabetical order + self.class.serialize_cstr(buf, options_str.split(//).sort.uniq.join) + end + + def serialize_max_key_element(buf, key) + buf.put(MAXKEY) + self.class.serialize_key(buf, key) + end + + def serialize_min_key_element(buf, key) + buf.put(MINKEY) + self.class.serialize_key(buf, key) + end + + def serialize_oid_element(buf, key, val) + buf.put(OID) + self.class.serialize_key(buf, key) + + buf.put_array(val.to_a) + end + + def serialize_string_element(buf, key, val, type) + buf.put(type) + self.class.serialize_key(buf, key) + + # Make a hole for the length + len_pos = buf.position + buf.put_int(0) + + # Save the string + start_pos = buf.position + self.class.serialize_cstr(buf, val) + end_pos = buf.position + + # Put the string size in front + buf.put_int(end_pos - start_pos, len_pos) + + # Go back to where we were + buf.position = end_pos + end + + def serialize_code_w_scope(buf, key, val) + buf.put(CODE_W_SCOPE) + self.class.serialize_key(buf, key) + + # Make a hole for the length + len_pos = buf.position + buf.put_int(0) + + buf.put_int(val.length + 1) + self.class.serialize_cstr(buf, val) + buf.put_array(BSON_CODER.new.serialize(val.scope).to_a) + + end_pos = buf.position + buf.put_int(end_pos - len_pos, len_pos) + buf.position = end_pos + end + + def deserialize_cstr(buf) + chars = "" + while true + b = buf.get + break if b == 0 + chars << b.chr + end + if RUBY_VERSION >= '1.9' + chars.force_encoding("utf-8") # Mongo stores UTF-8 + end + chars + end + + def bson_type(o) + case o + when nil + NULL + when Integer + NUMBER_INT + when Float + NUMBER + when ByteBuffer + BINARY + when Code + CODE_W_SCOPE + when String + STRING + when Array + ARRAY + when Regexp + REGEX + when ObjectID + OID + when DBRef + REF + when true, false + BOOLEAN + when Time + DATE + when Hash + OBJECT + when Symbol + SYMBOL + when MaxKey + MAXKEY + when MinKey + MINKEY + when Numeric + raise InvalidDocument, "Cannot serialize the Numeric type #{o.class} as BSON; only Fixum, Bignum, and Float are supported." + when Date, DateTime + raise InvalidDocument, "#{o.class} is not currently supported; " + + "use a UTC Time instance instead." + else + if defined?(ActiveSupport::TimeWithZone) && o.is_a?(ActiveSupport::TimeWithZone) + raise InvalidDocument, "ActiveSupport::TimeWithZone is not currently supported; " + + "use a UTC Time instance instead." + else + raise InvalidDocument, "Cannot serialize #{o.class} as a BSON type; it either isn't supported or won't translate to BSON." + end + end + end + + end end diff --git a/lib/mongo_bson/types/binary.rb b/lib/mongo_bson/types/binary.rb index 6f21bd3..f9e3ba2 100644 --- a/lib/mongo_bson/types/binary.rb +++ b/lib/mongo_bson/types/binary.rb @@ -14,7 +14,7 @@ # limitations under the License. # ++ -require 'mongo/util/byte_buffer' +require 'mongo_bson/byte_buffer' module Mongo diff --git a/mongo-ruby-driver.gemspec b/mongo-ruby-driver.gemspec index f6f04b4..fd6d2bf 100644 --- a/mongo-ruby-driver.gemspec +++ b/mongo-ruby-driver.gemspec @@ -12,11 +12,12 @@ Gem::Specification.new do |s| s.require_paths = ['lib'] s.files = ['README.rdoc', 'Rakefile', 'mongo-ruby-driver.gemspec', 'LICENSE.txt'] - s.files += Dir['lib/**/*.rb'] + Dir['examples/**/*.rb'] + Dir['bin/**/*.rb'] + s.files += Dir['lib/mongo/*.rb'] + Dir['examples/**/*.rb'] + Dir['bin/**/*.rb'] s.test_files = Dir['test/**/*.rb'] s.has_rdoc = true s.test_files = Dir['test/**/*.rb'] + s.test_files -= Dir['test/mongo_bson/*.rb'] # remove these files from the manifest s.has_rdoc = true s.rdoc_options = ['--main', 'README.rdoc', '--inline-source'] diff --git a/test/chunk_test.rb b/test/chunk_test.rb index f7a0fda..f3f06df 100644 --- a/test/chunk_test.rb +++ b/test/chunk_test.rb @@ -1,3 +1,4 @@ +$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'test/test_helper' require 'mongo/gridfs' diff --git a/test/mongo_bson/bson_test.rb b/test/mongo_bson/bson_test.rb index 139a177..aff0bdf 100644 --- a/test/mongo_bson/bson_test.rb +++ b/test/mongo_bson/bson_test.rb @@ -24,26 +24,26 @@ class BSONTest < Test::Unit::TestCase def test_string doc = {'doc' => 'hello, world'} - bson = bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_valid_utf8_string doc = {'doc' => 'aé'} - bson = bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_valid_utf8_key doc = {'aé' => 'hello'} - bson = bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_document_length doc = {'name' => 'a' * 5 * 1024 * 1024} assert_raise InvalidDocument do - assert BSON.serialize(doc) + assert Mongo::BSON_CODER.serialize(doc) end end @@ -55,7 +55,7 @@ class BSONTest < Test::Unit::TestCase string = Iconv.conv('iso-8859-1', 'utf-8', 'aé') doc = {'doc' => string} assert_raise InvalidStringEncoding do - BSON.serialize(doc) + Mongo::BSON_CODER.serialize(doc) end end @@ -63,51 +63,51 @@ class BSONTest < Test::Unit::TestCase key = Iconv.conv('iso-8859-1', 'utf-8', 'aé') doc = {key => 'hello'} assert_raise InvalidStringEncoding do - BSON.serialize(doc) + Mongo::BSON_CODER.serialize(doc) end end else def test_non_utf8_string - bson = BSON.serialize({'str' => 'aé'.encode('iso-8859-1')}) - result = BSON.deserialize(bson)['str'] + bson = Mongo::BSON_CODER.serialize({'str' => 'aé'.encode('iso-8859-1')}) + result = Mongo::BSON_CODER.deserialize(bson)['str'] assert_equal 'aé', result assert_equal 'UTF-8', result.encoding.name end def test_non_utf8_key - bson = BSON.serialize({'aé'.encode('iso-8859-1') => 'hello'}) - assert_equal 'hello', BSON.deserialize(bson)['aé'] + bson = Mongo::BSON_CODER.serialize({'aé'.encode('iso-8859-1') => 'hello'}) + assert_equal 'hello', Mongo::BSON_CODER.deserialize(bson)['aé'] end end def test_code doc = {'$where' => Code.new('this.a.b < this.b')} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_number doc = {'doc' => 41.99} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_int doc = {'doc' => 42} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) doc = {"doc" => -5600} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) doc = {"doc" => 2147483647} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) doc = {"doc" => -2147483648} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_ordered_hash @@ -116,32 +116,32 @@ class BSONTest < Test::Unit::TestCase doc["a"] = 2 doc["c"] = 3 doc["d"] = 4 - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_object doc = {'doc' => {'age' => 42, 'name' => 'Spongebob', 'shoe_size' => 9.5}} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_oid doc = {'doc' => ObjectID.new} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_array doc = {'doc' => [1, 2, 'a', 'b']} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_regex doc = {'doc' => /foobar/i} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) assert_equal doc, doc2 r = doc2['doc'] @@ -151,9 +151,9 @@ class BSONTest < Test::Unit::TestCase assert_equal 'zywcab', r.extra_options_str doc = {'doc' => r} - bson_doc = BSON.serialize(doc) + bson_doc = Mongo::BSON_CODER.serialize(doc) doc2 = nil - doc2 = BSON.deserialize(bson_doc) + doc2 = Mongo::BSON_CODER.deserialize(bson_doc) assert_equal doc, doc2 r = doc2['doc'] @@ -163,30 +163,30 @@ class BSONTest < Test::Unit::TestCase def test_boolean doc = {'doc' => true} - bson = BSON.serialize(doc) - assert_equal doc, BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson) end def test_date doc = {'date' => Time.now} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) # Mongo only stores up to the millisecond assert_in_delta doc['date'], doc2['date'], 0.001 end def test_date_returns_as_utc doc = {'date' => Time.now} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) assert doc2['date'].utc? end def test_date_before_epoch begin doc = {'date' => Time.utc(1600)} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) # Mongo only stores up to the millisecond assert_in_delta doc['date'], doc2['date'], 0.001 rescue ArgumentError @@ -201,7 +201,7 @@ class BSONTest < Test::Unit::TestCase [DateTime.now, Date.today, Zone].each do |invalid_date| doc = {:date => invalid_date} begin - bson = BSON.serialize(doc) + bson = Mongo::BSON_CODER.serialize(doc) rescue => e ensure assert_equal InvalidDocument, e.class @@ -214,16 +214,16 @@ class BSONTest < Test::Unit::TestCase oid = ObjectID.new doc = {} doc['dbref'] = DBRef.new('namespace', oid) - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) assert_equal 'namespace', doc2['dbref'].namespace assert_equal oid, doc2['dbref'].object_id end def test_symbol doc = {'sym' => :foo} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) assert_equal :foo, doc2['sym'] end @@ -232,8 +232,8 @@ class BSONTest < Test::Unit::TestCase 'binstring'.each_byte { |b| bin.put(b) } doc = {'bin' => bin} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal 'binstring', bin2.to_s @@ -244,8 +244,8 @@ class BSONTest < Test::Unit::TestCase bin = Binary.new([1, 2, 3, 4, 5], Binary::SUBTYPE_USER_DEFINED) doc = {'bin' => bin} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal [1, 2, 3, 4, 5], bin2.to_a @@ -257,8 +257,8 @@ class BSONTest < Test::Unit::TestCase 5.times { |i| bb.put(i + 1) } doc = {'bin' => bb} - bson = BSON.serialize(doc) - doc2 = BSON.deserialize(bson) + bson = Mongo::BSON_CODER.serialize(doc) + doc2 = Mongo::BSON_CODER.deserialize(bson) bin2 = doc2['bin'] assert_kind_of Binary, bin2 assert_equal [1, 2, 3, 4, 5], bin2.to_a @@ -269,24 +269,24 @@ class BSONTest < Test::Unit::TestCase val = OrderedHash.new val['not_id'] = 1 val['_id'] = 2 - roundtrip = BSON.deserialize(BSON.serialize(val, false, true).to_a) + roundtrip = Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(val, false, true).to_a) assert_kind_of OrderedHash, roundtrip assert_equal '_id', roundtrip.keys.first val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'} - roundtrip = BSON.deserialize(BSON.serialize(val, false, true).to_a) + roundtrip = Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(val, false, true).to_a) assert_kind_of OrderedHash, roundtrip assert_equal '_id', roundtrip.keys.first end def test_nil_id doc = {"_id" => nil} - assert_equal doc, BSON.deserialize(bson = BSON.serialize(doc, false, true).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(bson = Mongo::BSON_CODER.serialize(doc, false, true).to_a) end def test_timestamp val = {"test" => [4, 20]} - assert_equal val, BSON.deserialize([0x13, 0x00, 0x00, 0x00, + assert_equal val, Mongo::BSON_CODER.deserialize([0x13, 0x00, 0x00, 0x00, 0x11, 0x74, 0x65, 0x73, 0x74, 0x00, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, @@ -296,29 +296,29 @@ class BSONTest < Test::Unit::TestCase def test_overflow doc = {"x" => 2**75} assert_raise RangeError do - bson = BSON.serialize(doc) + bson = Mongo::BSON_CODER.serialize(doc) end doc = {"x" => 9223372036854775} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) doc = {"x" => 9223372036854775807} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) doc["x"] = doc["x"] + 1 assert_raise RangeError do - bson = BSON.serialize(doc) + bson = Mongo::BSON_CODER.serialize(doc) end doc = {"x" => -9223372036854775} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) doc = {"x" => -9223372036854775808} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) doc["x"] = doc["x"] - 1 assert_raise RangeError do - bson = BSON.serialize(doc) + bson = Mongo::BSON_CODER.serialize(doc) end end @@ -326,7 +326,7 @@ class BSONTest < Test::Unit::TestCase [BigDecimal.new("1.0"), Complex(0, 1), Rational(2, 3)].each do |type| doc = {"x" => type} begin - BSON.serialize(doc) + Mongo::BSON_CODER.serialize(doc) rescue => e ensure assert_equal InvalidDocument, e.class @@ -340,12 +340,12 @@ class BSONTest < Test::Unit::TestCase val['not_id'] = 1 val['_id'] = 2 assert val.keys.include?('_id') - BSON.serialize(val) + Mongo::BSON_CODER.serialize(val) assert val.keys.include?('_id') val = {'a' => 'foo', 'b' => 'bar', :_id => 42, 'z' => 'hello'} assert val.keys.include?(:_id) - BSON.serialize(val) + Mongo::BSON_CODER.serialize(val) assert val.keys.include?(:_id) end @@ -360,50 +360,50 @@ class BSONTest < Test::Unit::TestCase dup = {"_id" => "foo", :_id => "foo"} one = {"_id" => "foo"} - assert_equal BSON.serialize(one).to_a, BSON.serialize(dup).to_a + assert_equal Mongo::BSON_CODER.serialize(one).to_a, Mongo::BSON_CODER.serialize(dup).to_a end def test_no_duplicate_id_when_moving_id dup = {"_id" => "foo", :_id => "foo"} one = {:_id => "foo"} - assert_equal BSON.serialize(one, false, true).to_s, BSON.serialize(dup, false, true).to_s + assert_equal Mongo::BSON_CODER.serialize(one, false, true).to_s, Mongo::BSON_CODER.serialize(dup, false, true).to_s end def test_null_character doc = {"a" => "\x00"} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) assert_raise InvalidDocument do - BSON.serialize({"\x00" => "a"}) + Mongo::BSON_CODER.serialize({"\x00" => "a"}) end assert_raise InvalidDocument do - BSON.serialize({"a" => (Regexp.compile "ab\x00c")}) + Mongo::BSON_CODER.serialize({"a" => (Regexp.compile "ab\x00c")}) end end def test_max_key doc = {"a" => MaxKey.new} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) end def test_min_key doc = {"a" => MinKey.new} - assert_equal doc, BSON.deserialize(BSON.serialize(doc).to_a) + assert_equal doc, Mongo::BSON_CODER.deserialize(Mongo::BSON_CODER.serialize(doc).to_a) end def test_invalid_object o = Object.new assert_raise InvalidDocument do - BSON.serialize({:foo => o}) + Mongo::BSON_CODER.serialize({:foo => o}) end assert_raise InvalidDocument do - BSON.serialize({:foo => Date.today}) + Mongo::BSON_CODER.serialize({:foo => Date.today}) end end @@ -416,10 +416,10 @@ class BSONTest < Test::Unit::TestCase assert_equal ")\000\000\000\020_id\000\001\000\000\000\002text" + "\000\004\000\000\000abc\000\002key\000\004\000\000\000abc\000\000", - BSON.serialize(a, false, true).to_s + Mongo::BSON_CODER.serialize(a, false, true).to_s assert_equal ")\000\000\000\002text\000\004\000\000\000abc\000\002key" + "\000\004\000\000\000abc\000\020_id\000\001\000\000\000\000", - BSON.serialize(a, false, false).to_s + Mongo::BSON_CODER.serialize(a, false, false).to_s end def test_move_id_with_nested_doc @@ -433,11 +433,11 @@ class BSONTest < Test::Unit::TestCase assert_equal ">\000\000\000\020_id\000\003\000\000\000\002text" + "\000\004\000\000\000abc\000\003hash\000\034\000\000" + "\000\002text\000\004\000\000\000abc\000\020_id\000\002\000\000\000\000\000", - BSON.serialize(c, false, true).to_s + Mongo::BSON_CODER.serialize(c, false, true).to_s assert_equal ">\000\000\000\002text\000\004\000\000\000abc\000\003hash" + "\000\034\000\000\000\002text\000\004\000\000\000abc\000\020_id" + "\000\002\000\000\000\000\020_id\000\003\000\000\000\000", - BSON.serialize(c, false, false).to_s + Mongo::BSON_CODER.serialize(c, false, false).to_s end if defined?(HashWithIndifferentAccess) @@ -447,12 +447,12 @@ class BSONTest < Test::Unit::TestCase embedded['_id'] = ObjectID.new doc['_id'] = ObjectID.new doc['embedded'] = [embedded] - BSON.serialize(doc, false, true).to_a + Mongo::BSON_CODER.serialize(doc, false, true).to_a assert doc.has_key?("_id") assert doc['embedded'][0].has_key?("_id") doc['_id'] = ObjectID.new - BSON.serialize(doc, false, true).to_a + Mongo::BSON_CODER.serialize(doc, false, true).to_a assert doc.has_key?("_id") end end diff --git a/test/unit/db_test.rb b/test/unit/db_test.rb index 7f3c9f8..75ddfb0 100644 --- a/test/unit/db_test.rb +++ b/test/unit/db_test.rb @@ -7,8 +7,8 @@ class DBTest < Test::Unit::TestCase documents = [documents] unless documents.is_a?(Array) message = ByteBuffer.new message.put_int(0) - BSON.serialize_cstr(message, "#{db.name}.test") - documents.each { |doc| message.put_array(BSON.new.serialize(doc, true).to_a) } + Mongo::BSON_CODER..serialize_cstr(message, "#{db.name}.test") + documents.each { |doc| message.put_array(Mongo::BSON_CODER.new.serialize(doc, true).to_a) } message = db.add_message_headers(Mongo::Constants::OP_INSERT, message) end end