IS.UUID.V1 UUID V1 Time Based Generation
A Universally Unique Identifier (UUID) is an identifier standard used in many non-MultiValue databases and software to generate a Unique Id outside of using incremental numbers. A UUID is simply a 128-bit unique value that can be expressed as either a larger number, or a string.
The common format for a UUID is a string format that looks like the following:
The intent of a UUID is to generate and ID that can be used across different databases, and always be unique. This is different from a sequential number that would only be unique in the file, or table, itself and not across files, accounts, or databases.
There are several different versions of UUID. This article is based around the Version 1 of the RFC 4122 specification for UUIDs. Version 1 is used to generate a Unique Id based on the current date/time, a clock sequence value, and a specific Node ID.
The current type is the number of nanoseconds from October 15, 1585, 00:00:00:00000000. For those that don't know, this is the Date Zero for Gregorian Calendar. For those used to working with Epoch dates in Unix, or MultiValue day 0, this is a different Date Zero.
The other odd thing about this value is that the value is in Nano-Seconds, not Milliseconds format commonly found in other places.
The RFC 4122 asked that you using UTC dates and times, but does not require you to do so. It also doesn't not requires you to use Milliseconds even those there is the option to do so. Including Milliseconds in your time just allows you to have more precision, but the addition of the Click Sequence ID address the missing millisecond factor.
The code needed to generate this time value looks like:
GREGORIAN.OFFSET = (ICONV("10/15/1582","D") * -1) * TIMESTAMP = (DATE() + GREGORIAN.OFFSET) ;* Add Current Date TIMESTAMP = (TIMESTAMP * (60 * 60 * 24)) ;* Convert to seconds TIMESTAMP = (TIMESTAMP + TIME()) ;* Add current Seconds TIMESTAMP = (TIMESTAMP * 1000) ;* Convert to Milliseconds TIMESTAMP = (TIMESTAMP + 0) ;* Adds current Milliseconds TIMESTAMP = (TIMESTAMP * 10000) ;* Convert to nanoseconds
Clock Sequence Id
Since there is a possibility that a UUID is generated within the same second, or milliseconds, RCF 4122 include the requirement for a sequential Clock Id. The value allows a unique ID to still be created even within the same timestamp.
Because of this requirement, you must provide a way to keep track of this stateful information. The easiest way to keep track of this information, without require a read and write to a file, is using a Named-Common. The program attached uses Named-Common for this purpose.
The main drawback to Named-Commons is that they are Port, or PIB, specific, so the Node ID or the Timestamp needs to include the current port number. If you don't do this, then there is a high chance of creating the same UUID on more than one port.
The last part piece that makes this version of the UUID ID unique is the Node id. Originally, the Node ID was a network-card MAC address. Software developers started to move away from using the MAC address as the Node ID when they became more security consensus. Since the MAC address identified the physical hardware, and by extension the locations on a network the physical hardware resides, RFC 4122 was modified to allow a Random Value to be used in place of a MAC Address.
In a database, that doesn't transmit its information to other parties, I don't see an issue with using a MAC address as the Node Id, but I can also see the security concerns as well.
If you want to use the MAC address of your database server's hardware, it is easy to generate: Ipconfig /all for Windows or ifconfig for Linux. By pulling it dynamically, you don't have to worry about change a control record when you transmit your code to another machine, customer, or store location.
Another option is to buy a network card from the store, and hardcode the MAC address found on the box. The main problem with hardcoding the MAC address is that you if you plan on selling your software, or using your software on a machine other than your production machine, you have to remember to change this value or you will create id collisions.
Since I am try to keep this version of the program as fast as possible, and still have an unique value, I decided to create a psendo-random value for the Node ID instead of going the MAC address route.
The code I used for the Node ID includes the Port ,or PIB, in the 1st part of the Node Id. This way there is an additional uniqueness across ports.
The Node Id starts with a hex value of "17", with the next 2 Hex Values representing the Port, or PIB, generated from OCONV("","U55BB"). The remaining 3 bytes are randomly generated. Since I don't want to have to calculate this Node ID each time I call this program, I store it in the Name-Common area just like the Clock ID.
While creating a structured Node ID like this is technically not following the RFC 4122 specification, as long as you start the node ID with a hex of '17', it will notify any UUID parser that this is not a MAC address, but a user generated value.
Now, keep in mind, if you use a MAC address as the Node Id instead of the calculated value I suggested above, you will have an issue with ID collisions due the lack of Port, or PIB, uniqueness. In that case I would suggest adding the Port, or PIB, to the timestamp as the nanosecond value.
TIMESTAMP = (TIMESTAMP * 10000) ;* Convert to nanoseconds * PORT.NO = FIELD(OCONV('','U50BB'),' ',1) TIMESTAMP = (TIMESTAMP + PORT.NO)
Since most clock values do not provided nanosecond timestamps, this would not cause you any problems as long as the port, or PIB, is smaller than 10000.
Conforming to RFC 4122 Specification
The last thing that needs to be done to make this a valid RFC 4122 value: There are two more values that must be specified.
- Set the four most significant bits of the 7th byte '0100', so that the Hex value always starts with a '1'
- Set the 2 most significant bits of the 9th byte to '10', so that the Hex value will always start with a 8, 9, A , or B
945EDC05-1033-11E6-80C0-170005B4F09D ^ ^ 1 2
This is all done using BITAND and BITOR. To keep this program generic, I chose to use Bit math to do the logical AND and OR, instead of the built-in BITAND and BITOR found on some systems.
The code to replicate the Logical AND and OR was borrowed from Dave Meagher's code found in the FOSS4MV/mvCrypt code on BitBucket:
FOR I = 1 TO 100 CALL IS.UUID.V1(UUID,ERROR) CRT UUID : " Error: " : ERROR NEXT I
945EDC05-1033-11E6-80C0-170005B4F09D Error: 0 945EDC05-1033-11E6-80C1-170005B4F09D Error: 0 945EDC05-1033-11E6-80C2-170005B4F09D Error: 0 945EDC05-1033-11E6-80C3-170005B4F09D Error: 0 945EDC05-1033-11E6-80C4-170005B4F09D Error: 0 945EDC05-1033-11E6-80C5-170005B4F09D Error: 0