Class: HyperUuid::Uuid
- Inherits:
-
Object
- Object
- HyperUuid::Uuid
- Includes:
- Comparable
- Defined in:
- lib/hyperuuid/uuid.rb
Overview
A parsed 16-byte RFC 9562 UUID value. Minimal by design — this gem has no runtime
dependency on the uuid gem, the same "no extra dependency" positioning as the Go
binding's purego-only/no-cgo approach and the Python binding's stdlib-ctypes-only one.
Constant Summary collapse
- NIL =
The RFC 9562 §5.9 Nil UUID — all 128 bits zero.
new(("\x00" * 16).b).freeze
- MAX =
The RFC 9562 §5.10 Max UUID — all 128 bits one.
new(("\xFF" * 16).b).freeze
Instance Attribute Summary collapse
-
#bytes ⇒ Object
readonly
The UUID's 16 raw bytes in RFC 9562 (big-endian) order.
Class Method Summary collapse
-
.parse(string) ⇒ Object
Parses an 8-4-4-4-12 hyphenated hex UUID string.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
Byte-order comparison against
other, ornilifotherisn't a Uuid. -
#==(other) ⇒ Object
(also: #eql?)
Whether
otherwraps the same 16 raw bytes. -
#from_sql_order ⇒ Object
Inverse of #to_sql_order — converts a SQL-Server-ordered version 6 or 7 UUID back to RFC 9562 order.
-
#hash ⇒ Object
Hash code consistent with #==, based on the raw bytes.
-
#initialize(bytes) ⇒ Uuid
constructor
Wraps a raw 16-byte RFC 9562 (big-endian) UUID value.
-
#inspect ⇒ Object
Debug representation, e.g.
-
#timestamp(raise_on_mismatch: true) ⇒ Object
The UTC timestamp embedded in a version 6 or 7 UUID's timestamp field.
-
#to_s ⇒ Object
(also: #to_str)
The 8-4-4-4-12 hyphenated hex string representation.
-
#to_sql_order ⇒ Object
Converts an RFC 9562-ordered version 6 or 7 UUID to the byte order SQL Server's
uniqueidentifierneeds on the wire to sort by creation order. -
#variant ⇒ Object
The RFC 9562 variant bits (top two bits of octet 8).
-
#version ⇒ Object
The RFC 9562 version nibble (bits 48-51, the high nibble of octet 6).
Constructor Details
#initialize(bytes) ⇒ Uuid
Wraps a raw 16-byte RFC 9562 (big-endian) UUID value.
14 15 16 17 |
# File 'lib/hyperuuid/uuid.rb', line 14 def initialize(bytes) raise ArgumentError, "bytes must be exactly 16 bytes" unless bytes.bytesize == 16 @bytes = bytes.dup.force_encoding(Encoding::BINARY).freeze end |
Instance Attribute Details
#bytes ⇒ Object (readonly)
The UUID's 16 raw bytes in RFC 9562 (big-endian) order.
9 10 11 |
# File 'lib/hyperuuid/uuid.rb', line 9 def bytes @bytes end |
Class Method Details
.parse(string) ⇒ Object
Parses an 8-4-4-4-12 hyphenated hex UUID string.
28 29 30 31 32 |
# File 'lib/hyperuuid/uuid.rb', line 28 def self.parse(string) hex = string.delete("-") raise ArgumentError, "invalid UUID string: #{string.inspect}" unless hex.match?(/\A[0-9a-fA-F]{32}\z/) new([hex].pack("H*")) end |
Instance Method Details
#<=>(other) ⇒ Object
Byte-order comparison against other, or nil if other isn't a Uuid.
149 150 151 152 |
# File 'lib/hyperuuid/uuid.rb', line 149 def <=>(other) return nil unless other.is_a?(Uuid) bytes <=> other.bytes end |
#==(other) ⇒ Object Also known as: eql?
Whether other wraps the same 16 raw bytes.
138 139 140 |
# File 'lib/hyperuuid/uuid.rb', line 138 def ==(other) other.is_a?(Uuid) && bytes == other.bytes end |
#from_sql_order ⇒ Object
Inverse of #to_sql_order — converts a SQL-Server-ordered version 6 or 7 UUID back to RFC 9562 order.
A SQL-ordered value's version nibble sits at a different octet depending on which version produced it (octet 7's top nibble = 7 for v7-sql-order, octet 8's top nibble = 6 for v6-sql-order — #version itself assumes RFC order's octet 6 and can't tell these apart), so this checks both fixed positions directly rather than calling #version.
Order matters here and isn't arbitrary: octet 8 must be checked first. For v6-sql-order
it's deterministic (top nibble always 0x6, by construction), and for v7-sql-order it's
also deterministic but structurally excluded from ever reading 0x6 (its top two bits are
the fixed variant 10, so the nibble only ever lands in 0x8-0xB) — no collision either
way. Octet 7, by contrast, is not safe to check first: for v7-sql-order it's
deterministically 0x7, but for v6-sql-order it holds clock_seq's fully random low
byte, which has a real (~1-in-16) chance of a top nibble that also happens to read 0x7 —
confirmed by an actual test failure during development, not a hypothetical. Checking
octet 8 first rules v6 in or out unambiguously before octet 7's reading can matter.
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/hyperuuid/uuid.rb', line 125 def from_sql_order octet8_version = (bytes.getbyte(8) >> 4) & 0x0F octet7_version = (bytes.getbyte(7) >> 4) & 0x0F if octet8_version == 6 self.class.new(Runtime.v6_to_rfc_order(bytes)) elsif octet7_version == 7 self.class.new(Runtime.v7_to_rfc_order(bytes)) else raise ArgumentError, "from_sql_order: not a recognized version 6 or 7 SQL-ordered UUID" end end |
#hash ⇒ Object
Hash code consistent with #==, based on the raw bytes.
144 145 146 |
# File 'lib/hyperuuid/uuid.rb', line 144 def hash bytes.hash end |
#inspect ⇒ Object
Debug representation, e.g. #<HyperUuid::Uuid ...>.
155 156 157 |
# File 'lib/hyperuuid/uuid.rb', line 155 def inspect "#<HyperUuid::Uuid #{self}>" end |
#timestamp(raise_on_mismatch: true) ⇒ Object
The UTC timestamp embedded in a version 6 or 7 UUID's timestamp field. Only meaningful
when version is 6 or 7 — the RFC 9562 bit layout doesn't distinguish "not a time-based
UUID" from "time-based UUID with a very early timestamp", so the caller is responsible
for checking version first if that matters.
Raises by default for any other version; pass raise_on_mismatch: false to get nil
back instead — for a caller that doesn't already know (or want to separately check)
whether this UUID is time-based.
59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/hyperuuid/uuid.rb', line 59 def (raise_on_mismatch: true) millis = case version when 6 then Runtime.v6_unix_millis(bytes) when 7 then Runtime.v7_unix_millis(bytes) else raise ArgumentError, "timestamp is only defined for version 6 or 7 UUIDs, got version #{version}" if raise_on_mismatch return nil end Time.at(millis / 1000, millis % 1000, :millisecond).utc end |
#to_s ⇒ Object Also known as: to_str
The 8-4-4-4-12 hyphenated hex string representation.
45 46 47 48 |
# File 'lib/hyperuuid/uuid.rb', line 45 def to_s hex = bytes.unpack1("H*") "#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}" end |
#to_sql_order ⇒ Object
Converts an RFC 9562-ordered version 6 or 7 UUID to the byte order SQL Server's
uniqueidentifier needs on the wire to sort by creation order. Dispatches on version
the same way #timestamp does.
System.Data.SqlTypes.SqlGuid comparison — and therefore T-SQL ORDER BY on a
uniqueidentifier column — doesn't compare a GUID's 16 bytes left to right; it uses a
fixed, non-sequential byte significance order (octets 10,11,12,13,14,15,8,9,6,7,4,5,
0,1,2,3, most significant first). Computed once in the native Rust core and verified
there (and independently, against the real SqlGuid comparator, in this project's C#
test suite); this binding calls the same native functions rather than reimplementing
the byte math.
For v7, this moves the timestamp and counter — the two fields that determine creation
order — into that comparison's most-significant bytes, and moves the trailing entropy,
which carries no ordering information, into the least-significant ones as one intact
block. For v6, which has no monotonic counter the way v7 does, the only field that
determines creation order is the 60-bit timestamp itself, so that moves into the most
significant bytes instead, with clock_seq/node (independently random per call, not
a counter, so no ordering value either way) relocated into the rest. v6's much simpler
byte layout needs no bit-level repacking to do this — just whole-octet-group
relocation — unlike v7's, and its version/variant land at different sql-order offsets
as a result (octet 8's top nibble / octet 6's top two bits, not 7/8).
v6-specific caveat, unlike v7: two version 6 UUIDs minted at the same millisecond have identical timestamp bits, so they aren't guaranteed to sort in creation order any more than plain RFC order already does — a pre-existing RFC 9562 v6 limitation, not one this transform introduces.
Meaningful only for a genuine version 6 or 7 UUID.
100 101 102 103 104 105 106 |
# File 'lib/hyperuuid/uuid.rb', line 100 def to_sql_order case version when 7 then self.class.new(Runtime.v7_to_sql_order(bytes)) when 6 then self.class.new(Runtime.v6_to_sql_order(bytes)) else raise ArgumentError, "to_sql_order is only defined for version 6 or 7 UUIDs, got version #{version}" end end |
#variant ⇒ Object
The RFC 9562 variant bits (top two bits of octet 8). 0b10 means RFC 9562/4122.
40 41 42 |
# File 'lib/hyperuuid/uuid.rb', line 40 def variant (bytes.getbyte(8) >> 6) & 0b11 end |
#version ⇒ Object
The RFC 9562 version nibble (bits 48-51, the high nibble of octet 6).
35 36 37 |
# File 'lib/hyperuuid/uuid.rb', line 35 def version (bytes.getbyte(6) >> 4) & 0x0F end |