Change string representation of ObjectID - add 'legacy' methods for interacting with old strings

This commit is contained in:
Mike Dirolf 2009-08-25 14:56:02 -04:00
parent e05c9fc5da
commit 47c34266cf
2 changed files with 91 additions and 55 deletions

View File

@ -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

View File

@ -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