diff --git a/lib/mongo/types/objectid.rb b/lib/mongo/types/objectid.rb index ec46b8b..70ccfc4 100644 --- a/lib/mongo/types/objectid.rb +++ b/lib/mongo/types/objectid.rb @@ -17,38 +17,17 @@ require 'mutex_m' require 'socket' require 'digest/md5' -require 'mongo/util/byte_buffer' module Mongo - # Implementation of the Babble OID. Object ids are not required by - # Mongo, but they make certain operations more efficient. - # - # The driver does not automatically assign ids to records that are - # inserted. (An upcoming feature will allow you to give an id "factory" - # to a database and/or a collection.) - # - # 12 bytes - # --- - # 0 time - # 1 - # 2 - # 3 - # 4 machine - # 5 - # 6 - # 7 pid - # 8 - # 9 inc - # 10 - # 11 + # Representation of an ObjectId for Mongo. class ObjectID - # The string representation of an OID is different than its internal - # and BSON byte representations. The BYTE_ORDER here maps - # internal/BSON byte position (the index in BYTE_ORDER) to the - # position of the two hex characters representing that byte in the - # string representation. For example, the 0th BSON byte corresponds to - # the (0-based) 7th pair of hex chars in the string. + # This is the legacy byte ordering for Babble. Versions of the Ruby + # driver prior to 0.14 used this byte ordering when converting ObjectID + # instances to and from strings. If you have string representations of + # ObjectIDs using the legacy byte ordering make sure to use the + # to_s_legacy and from_string_legacy methods, or convert your strings + # with ObjectID#legacy_string_convert BYTE_ORDER = [7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8] LOCK = Object.new @@ -56,17 +35,6 @@ module Mongo @@index = 0 - # Given a string representation of an ObjectID, return a new ObjectID - # with that value. - def self.from_string(str) - raise "illegal ObjectID format" unless legal?(str) - data = [] - BYTE_ORDER.each_with_index { |string_position, data_index| - data[data_index] = str[string_position * 2, 2].to_i(16) - } - self.new(data) - end - def self.legal?(str) len = BYTE_ORDER.length * 2 str =~ /([0-9a-f]+)/i @@ -88,7 +56,42 @@ module Mongo @data.dup end + # Given a string representation of an ObjectID, return a new ObjectID + # with that value. + def self.from_string(str) + raise "illegal ObjectID format" unless legal?(str) + data = [] + 12.times do |i| + data[i] = str[i * 2, 2].to_i(16) + end + self.new(data) + end + + # Create a new ObjectID given a string representation of an ObjectID + # using the legacy byte ordering. This method may eventually be + # removed. If you are not sure that you need this method you should be + # using the regular from_string. + def self.from_string_legacy(str) + raise "illegal ObjectID format" unless legal?(str) + data = [] + BYTE_ORDER.each_with_index { |string_position, data_index| + data[data_index] = str[string_position * 2, 2].to_i(16) + } + self.new(data) + end + def to_s + str = ' ' * 24 + 12.times do |i| + str[i * 2, 2] = '%02x' % @data[i] + end + str + end + + # Get a string representation of this ObjectID using the legacy byte + # ordering. This method may eventually be removed. If you are not sure + # that you need this method you should be using the regular to_s. + def to_s_legacy str = ' ' * 24 BYTE_ORDER.each_with_index { |string_position, data_index| str[string_position * 2, 2] = '%02x' % @data[data_index] @@ -96,33 +99,37 @@ module Mongo str end + # Convert a string representation of an ObjectID using the legacy byte + # ordering to the proper byte ordering. This method may eventually be + # removed. If you are not sure that you need this method it is probably + # unnecessary. + def self.legacy_string_convert(str) + legacy = ' ' * 24 + BYTE_ORDER.each_with_index do |legacy_pos, pos| + legacy[legacy_pos * 2, 2] = str[pos * 2, 2] + end + legacy + end + private def generate + oid = '' + # 4 bytes current time time = Time.new.to_i - buf = ByteBuffer.new - buf.put_int(time & 0xFFFFFFFF) + oid += [time].pack("N") # 3 bytes machine - machine_hash = Digest::MD5.digest(Socket.gethostname) - buf.put(machine_hash[0]) - buf.put(machine_hash[1]) - buf.put(machine_hash[2]) + oid += Digest::MD5.digest(Socket.gethostname)[0, 3] # 2 bytes pid - pid = Process.pid % 0xFFFF - buf.put(pid & 0xFF) - buf.put((pid >> 8) & 0xFF) + oid += [Process.pid % 0xFFFF].pack("n") # 3 bytes inc - inc = get_inc - buf.put(inc & 0xFF) - buf.put((inc >> 8) & 0xFF) - buf.put((inc >> 16) & 0xFF) + oid += [get_inc].pack("N")[1, 3] - buf.rewind - buf.to_a.dup + oid.unpack("C12") end def get_inc diff --git a/test/test_objectid.rb b/test/test_objectid.rb index b41c8f5..ccd03df 100644 --- a/test/test_objectid.rb +++ b/test/test_objectid.rb @@ -29,6 +29,15 @@ class ObjectIDTest < Test::Unit::TestCase assert_equal 24, $1.length end + def test_to_s_legacy + s = @o.to_s_legacy + assert_equal 24, s.length + s =~ /^([0-9a-f]+)$/ + assert_equal 24, $1.length + + assert_not_equal s, @o.to_s + end + def test_save_and_restore host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT @@ -51,6 +60,14 @@ class ObjectIDTest < Test::Unit::TestCase assert_equal @o.to_s, o2.to_s end + def test_from_string_legacy + hex_str = @o.to_s_legacy + o2 = ObjectID.from_string_legacy(hex_str) + assert_equal hex_str, o2.to_s_legacy + assert_equal @o, o2 + assert_equal @o.to_s, o2.to_s + end + def test_legal assert !ObjectID.legal?(nil) assert !ObjectID.legal?("fred") @@ -62,7 +79,7 @@ class ObjectIDTest < Test::Unit::TestCase end def test_from_string_leading_zeroes - hex_str = '000000000000000000abcdef' + hex_str = '000000000000000000000000' o = ObjectID.from_string(hex_str) assert_equal hex_str, o.to_s end @@ -70,7 +87,19 @@ class ObjectIDTest < Test::Unit::TestCase def test_byte_order hex_str = '000102030405060708090A0B' o = ObjectID.from_string(hex_str) + assert_equal [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b], o.to_a + end + + def test_legacy_byte_order + hex_str = '000102030405060708090A0B' + o = ObjectID.from_string_legacy(hex_str) assert_equal [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0b, 0x0a, 0x09, 0x08], o.to_a end + def test_legacy_string_convert + l = @o.to_s_legacy + s = @o.to_s + assert_equal s, ObjectID.legacy_string_convert(l) + end + end