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 'mutex_m'
require 'socket' require 'socket'
require 'digest/md5' require 'digest/md5'
require 'mongo/util/byte_buffer'
module Mongo module Mongo
# Implementation of the Babble OID. Object ids are not required by # Representation of an ObjectId for Mongo.
# 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
class ObjectID class ObjectID
# The string representation of an OID is different than its internal # This is the legacy byte ordering for Babble. Versions of the Ruby
# and BSON byte representations. The BYTE_ORDER here maps # driver prior to 0.14 used this byte ordering when converting ObjectID
# internal/BSON byte position (the index in BYTE_ORDER) to the # instances to and from strings. If you have string representations of
# position of the two hex characters representing that byte in the # ObjectIDs using the legacy byte ordering make sure to use the
# string representation. For example, the 0th BSON byte corresponds to # to_s_legacy and from_string_legacy methods, or convert your strings
# the (0-based) 7th pair of hex chars in the string. # with ObjectID#legacy_string_convert
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
@ -56,17 +35,6 @@ module Mongo
@@index = 0 @@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) def self.legal?(str)
len = BYTE_ORDER.length * 2 len = BYTE_ORDER.length * 2
str =~ /([0-9a-f]+)/i str =~ /([0-9a-f]+)/i
@ -88,7 +56,42 @@ module Mongo
@data.dup @data.dup
end 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 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 str = ' ' * 24
BYTE_ORDER.each_with_index { |string_position, data_index| BYTE_ORDER.each_with_index { |string_position, data_index|
str[string_position * 2, 2] = '%02x' % @data[data_index] str[string_position * 2, 2] = '%02x' % @data[data_index]
@ -96,33 +99,37 @@ module Mongo
str str
end 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 private
def generate def generate
oid = ''
# 4 bytes current time # 4 bytes current time
time = Time.new.to_i time = Time.new.to_i
buf = ByteBuffer.new oid += [time].pack("N")
buf.put_int(time & 0xFFFFFFFF)
# 3 bytes machine # 3 bytes machine
machine_hash = Digest::MD5.digest(Socket.gethostname) oid += Digest::MD5.digest(Socket.gethostname)[0, 3]
buf.put(machine_hash[0])
buf.put(machine_hash[1])
buf.put(machine_hash[2])
# 2 bytes pid # 2 bytes pid
pid = Process.pid % 0xFFFF oid += [Process.pid % 0xFFFF].pack("n")
buf.put(pid & 0xFF)
buf.put((pid >> 8) & 0xFF)
# 3 bytes inc # 3 bytes inc
inc = get_inc oid += [get_inc].pack("N")[1, 3]
buf.put(inc & 0xFF)
buf.put((inc >> 8) & 0xFF)
buf.put((inc >> 16) & 0xFF)
buf.rewind oid.unpack("C12")
buf.to_a.dup
end end
def get_inc def get_inc

View File

@ -29,6 +29,15 @@ class ObjectIDTest < Test::Unit::TestCase
assert_equal 24, $1.length assert_equal 24, $1.length
end 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 def test_save_and_restore
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
port = ENV['MONGO_RUBY_DRIVER_PORT'] || Connection::DEFAULT_PORT 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 assert_equal @o.to_s, o2.to_s
end 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 def test_legal
assert !ObjectID.legal?(nil) assert !ObjectID.legal?(nil)
assert !ObjectID.legal?("fred") assert !ObjectID.legal?("fred")
@ -62,7 +79,7 @@ class ObjectIDTest < Test::Unit::TestCase
end end
def test_from_string_leading_zeroes def test_from_string_leading_zeroes
hex_str = '000000000000000000abcdef' hex_str = '000000000000000000000000'
o = ObjectID.from_string(hex_str) o = ObjectID.from_string(hex_str)
assert_equal hex_str, o.to_s assert_equal hex_str, o.to_s
end end
@ -70,7 +87,19 @@ class ObjectIDTest < Test::Unit::TestCase
def test_byte_order def test_byte_order
hex_str = '000102030405060708090A0B' hex_str = '000102030405060708090A0B'
o = ObjectID.from_string(hex_str) 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 assert_equal [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0b, 0x0a, 0x09, 0x08], o.to_a
end 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 end