Commit 0833d668 authored by Tatiana A. Nurnberg's avatar Tatiana A. Nurnberg

Bug#35848: UUID() returns UUIDs with the wrong time

offset for time part in UUIDs was 1/1000 of what it
should be. In other words, offset was off.

Also handle the case where we count into the future
when several UUIDs are generated in one "tick", and
then the next call is late enough for us to unwind
some but not all of those borrowed ticks.

Lastly, handle the case where we keep borrowing and
borrowing until the tick-counter overflows by also
changing into a new "numberspace" by creating a new
random suffix.


mysql-test/r/func_misc.result:
  Show that time-part of UUIDs is correct now.
mysql-test/t/func_misc.test:
  Show that time-part of UUIDs is correct now
  by replicating the C-code's resultin SQL.
  Results also decode to expect date-data on
  command-line (external validation).
  
  No test for unwinding of borrowed ticks as
  this a) is a race and b) depends on what timer
  we get.
sql/item_strfunc.cc:
  correct offset for date/time-part of UUID.
  also make sure that when we counted into
  the future earlier (several UUIDs generated
  in same tick), we only give back as many
  "borrowed" ticks as we can without duplicating
  past timestamps. If our tick-counter overflows
  before we can give back, or if the system-clock
  is set back (by user or Daylight Saving Time),
  we create a new random suffix to avoid
  collisions and clear the tick-counter.
parent b93fcb9b
......@@ -305,4 +305,18 @@ drop table t1;
SELECT NAME_CONST('var', 'value') COLLATE latin1_general_cs;
NAME_CONST('var', 'value') COLLATE latin1_general_cs
value
select @@session.time_zone into @save_tz;
set @@session.time_zone='UTC';
select uuid() into @my_uuid;
select mid(@my_uuid,15,1);
mid(@my_uuid,15,1)
1
select 24 * 60 * 60 * 1000 * 1000 * 10 into @my_uuid_one_day;
select concat('0',mid(@my_uuid,16,3),mid(@my_uuid,10,4),left(@my_uuid,8)) into @my_uuidate;
select floor(conv(@my_uuidate,16,10)/@my_uuid_one_day) into @my_uuid_date;
select 141427 + datediff(curdate(),'1970-01-01') into @my_uuid_synthetic;
select @my_uuid_date - @my_uuid_synthetic;
@my_uuid_date - @my_uuid_synthetic
0
set @@session.time_zone=@save_tz;
End of 5.0 tests
......@@ -417,5 +417,24 @@ drop table t1;
#
SELECT NAME_CONST('var', 'value') COLLATE latin1_general_cs;
#
# Bug #35848: UUID() returns UUIDs with the wrong time
#
select @@session.time_zone into @save_tz;
# make sure all times are UTC so the DayNr won't differ
set @@session.time_zone='UTC';
select uuid() into @my_uuid;
# if version nibble isn't 1, the following calculations will be rubbish
select mid(@my_uuid,15,1);
select 24 * 60 * 60 * 1000 * 1000 * 10 into @my_uuid_one_day;
select concat('0',mid(@my_uuid,16,3),mid(@my_uuid,10,4),left(@my_uuid,8)) into @my_uuidate;
select floor(conv(@my_uuidate,16,10)/@my_uuid_one_day) into @my_uuid_date;
select 141427 + datediff(curdate(),'1970-01-01') into @my_uuid_synthetic;
# these should be identical; date part of UUID should be current date
select @my_uuid_date - @my_uuid_synthetic;
set @@session.time_zone=@save_tz;
--echo End of 5.0 tests
......@@ -3258,7 +3258,8 @@ static char clock_seq_and_node_str[]="-0000-000000000000";
/* number of 100-nanosecond intervals between
1582-10-15 00:00:00.00 and 1970-01-01 00:00:00.00 */
#define UUID_TIME_OFFSET ((ulonglong) 141427 * 24 * 60 * 60 * 1000 * 10 )
#define UUID_TIME_OFFSET ((ulonglong) 141427 * 24 * 60 * 60 * \
1000 * 1000 * 10)
#define UUID_VERSION 0x1000
#define UUID_VARIANT 0x8000
......@@ -3317,24 +3318,64 @@ String *Item_func_uuid::val_str(String *str)
set_clock_seq_str();
}
ulonglong tv=my_getsystime() + UUID_TIME_OFFSET + nanoseq;
if (unlikely(tv < uuid_time))
set_clock_seq_str();
else if (unlikely(tv == uuid_time))
ulonglong tv= my_getsystime() + UUID_TIME_OFFSET + nanoseq;
if (likely(tv > uuid_time))
{
/*
Current time is ahead of last timestamp, as it should be.
If we "borrowed time", give it back, just as long as we
stay ahead of the previous timestamp.
*/
if (nanoseq)
{
/* special protection from low-res system clocks */
nanoseq++;
tv++;
DBUG_ASSERT((tv > uuid_time) && (nanoseq > 0));
/*
-1 so we won't make tv= uuid_time for nanoseq >= (tv - uuid_time)
*/
long delta= min(nanoseq, tv - uuid_time -1);
tv-= delta;
nanoseq-= delta;
}
}
else
{
if (nanoseq)
if (unlikely(tv == uuid_time))
{
tv-=nanoseq;
nanoseq=0;
/*
For low-res system clocks. If several requests for UUIDs
end up on the same tick, we add a nano-second to make them
different.
( current_timestamp + nanoseq * calls_in_this_period )
may end up > next_timestamp; this is OK. Nonetheless, we'll
try to unwind nanoseq when we get a chance to.
If nanoseq overflows, we'll start over with a new numberspace
(so the if() below is needed so we can avoid the ++tv and thus
match the follow-up if() if nanoseq overflows!).
*/
if (likely(++nanoseq))
++tv;
}
if (unlikely(tv <= uuid_time))
{
/*
If the admin changes the system clock (or due to Daylight
Saving Time), the system clock may be turned *back* so we
go through a period once more for which we already gave out
UUIDs. To avoid duplicate UUIDs despite potentially identical
times, we make a new random component.
We also come here if the nanoseq "borrowing" overflows.
In either case, we throw away any nanoseq borrowing since it's
irrelevant in the new numberspace.
*/
set_clock_seq_str();
tv= my_getsystime() + UUID_TIME_OFFSET;
nanoseq= 0;
DBUG_PRINT("uuid",("making new numberspace"));
}
DBUG_ASSERT(tv > uuid_time);
}
uuid_time=tv;
pthread_mutex_unlock(&LOCK_uuid_generator);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment