github.com/muyo/sno@v1.2.1/id.go (about) 1 package sno 2 3 import ( 4 "bytes" 5 "database/sql/driver" 6 "encoding/binary" 7 "time" 8 "unsafe" 9 10 "github.com/muyo/sno/internal" 11 ) 12 13 const ( 14 // SizeBinary is the length of an ID in its binary array representation. 15 SizeBinary = 10 16 17 // SizeEncoded is the length of an ID in its canonical base-32 encoded representation. 18 SizeEncoded = 16 19 20 // Epoch is the offset to the Unix epoch, in seconds, that ID timestamps are embedded with. 21 // Corresponds to 2010-01-01 00:00:00 UTC. 22 Epoch = 1262304000 23 epochNsec = Epoch * 1e9 24 25 // TimeUnit is the time unit timestamps are embedded with - 4msec, as expressed in nanoseconds. 26 TimeUnit = 4e6 27 28 // MaxTimestamp is the max number of time units that can be embedded in an ID's timestamp. 29 // Corresponds to 2079-09-07 15:47:35.548 UTC in our custom epoch. 30 MaxTimestamp = 1<<39 - 1 31 32 // MaxPartition is the max Partition number when represented as a uint16. 33 // It equals max uint16 (65535) and is the equivalent of Partition{255, 255}. 34 MaxPartition = 1<<16 - 1 35 36 // MaxSequence is the max sequence number supported by generators. As bounds can be set 37 // individually - this is the upper cap and equals max uint16 (65535). 38 MaxSequence = 1<<16 - 1 39 ) 40 41 // ID is the binary representation of a sno ID. 42 // 43 // It is comprised of 10 bytes in 2 blocks of 40 bits, with its components stored in big-endian order. 44 // 45 // The timestamp: 46 // 39 bits - unsigned milliseconds since epoch with a 4msec resolution 47 // 1 bit - the tick-tock toggle 48 // 49 // The payload: 50 // 8 bits - metabyte 51 // 16 bits - partition 52 // 16 bits - sequence 53 // 54 type ID [SizeBinary]byte 55 56 // Time returns the timestamp of the ID as a time.Time struct. 57 func (id ID) Time() time.Time { 58 var ( 59 units = int64(binary.BigEndian.Uint64(id[:]) >> 25) 60 s = units/250 + Epoch 61 ns = (units % 250) * TimeUnit 62 ) 63 64 return time.Unix(s, ns) 65 } 66 67 // Timestamp returns the timestamp of the ID as nanoseconds relative to the Unix epoch. 68 func (id ID) Timestamp() int64 { 69 return int64(binary.BigEndian.Uint64(id[:])>>25)*TimeUnit + epochNsec 70 } 71 72 // Meta returns the metabyte of the ID. 73 func (id ID) Meta() byte { 74 return id[5] 75 } 76 77 // Partition returns the partition of the ID. 78 func (id ID) Partition() Partition { 79 return Partition{id[6], id[7]} 80 } 81 82 // Sequence returns the sequence of the ID. 83 func (id ID) Sequence() uint16 { 84 return uint16(id[8])<<8 | uint16(id[9]) 85 } 86 87 // IsZero checks whether the ID is a zero value. 88 func (id ID) IsZero() bool { 89 return id == zero 90 } 91 92 // String implements fmt.Stringer by returning the base32-encoded representation of the ID 93 // as a string. 94 func (id ID) String() string { 95 enc := internal.Encode((*[10]byte)(&id)) 96 dst := enc[:] 97 98 return *(*string)(unsafe.Pointer(&dst)) 99 } 100 101 // Bytes returns the ID as a byte slice. 102 func (id ID) Bytes() []byte { 103 return id[:] 104 } 105 106 // MarshalBinary implements encoding.BinaryMarshaler by returning the ID as a byte slice. 107 func (id ID) MarshalBinary() ([]byte, error) { 108 return id[:], nil 109 } 110 111 // UnmarshalBinary implements encoding.BinaryUnmarshaler by copying src into the receiver. 112 func (id *ID) UnmarshalBinary(src []byte) error { 113 if len(src) != SizeBinary { 114 return &InvalidDataSizeError{Size: len(src)} 115 } 116 117 copy(id[:], src) 118 119 return nil 120 } 121 122 // MarshalText implements encoding.TextMarshaler by returning the base32-encoded representation 123 // of the ID as a byte slice. 124 func (id ID) MarshalText() ([]byte, error) { 125 b := internal.Encode((*[10]byte)(&id)) 126 127 return b[:], nil 128 } 129 130 // UnmarshalText implements encoding.TextUnmarshaler by decoding a base32-encoded representation 131 // of the ID from src into the receiver. 132 func (id *ID) UnmarshalText(src []byte) error { 133 if len(src) != SizeEncoded { 134 return &InvalidDataSizeError{Size: len(src)} 135 } 136 137 *id = internal.Decode(src) 138 139 return nil 140 } 141 142 // MarshalJSON implements encoding.json.Marshaler by returning the base32-encoded and quoted 143 // representation of the ID as a byte slice. 144 // 145 // If the ID is a zero value, MarshalJSON will return a byte slice containing 'null' (unquoted) instead. 146 // 147 // Note that ID's are byte arrays and Go's std (un)marshaler is unable to distinguish 148 // the zero values of custom structs as "empty", so the 'omitempty' tag has the same caveats 149 // as, for example, time.Time. 150 // 151 // See https://github.com/golang/go/issues/11939 for tracking purposes as changes are being 152 // discussed. 153 func (id ID) MarshalJSON() ([]byte, error) { 154 if id == zero { 155 return []byte("null"), nil 156 } 157 158 dst := []byte("\" \"") 159 enc := internal.Encode((*[10]byte)(&id)) 160 copy(dst[1:], enc[:]) 161 162 return dst, nil 163 } 164 165 // UnmarshalJSON implements encoding.json.Unmarshaler by decoding a base32-encoded and quoted 166 // representation of an ID from src into the receiver. 167 // 168 // If the byte slice is an unquoted 'null', the receiving ID will instead be set 169 // to a zero ID. 170 func (id *ID) UnmarshalJSON(src []byte) error { 171 n := len(src) 172 if n != SizeEncoded+2 { 173 if n == 4 && src[0] == 'n' && src[1] == 'u' && src[2] == 'l' && src[3] == 'l' { 174 *id = zero 175 return nil 176 } 177 178 return &InvalidDataSizeError{Size: n} 179 } 180 181 *id = internal.Decode(src[1 : n-1]) 182 183 return nil 184 } 185 186 // Compare returns an integer comparing this and that ID lexicographically. 187 // 188 // Returns: 189 // 0 - if this and that are equal, 190 // -1 - if this is smaller than that, 191 // 1 - if this is greater than that. 192 // 193 // Note that IDs are byte arrays - if all you need is to check for equality, a simple... 194 // if thisID == thatID {...} 195 // ... will do the trick. 196 func (id ID) Compare(that ID) int { 197 return bytes.Compare(id[:], that[:]) 198 } 199 200 // Value implements the sql.driver.Valuer interface by returning the ID as a byte slice. 201 // If you'd rather receive a string, wrapping an ID is a possible solution... 202 // 203 // // stringedID wraps a sno ID to provide a driver.Valuer implementation which 204 // // returns strings. 205 // type stringedID sno.ID 206 // 207 // func (id stringedID) Value() (driver.Value, error) { 208 // return sno.ID(id).String(), nil 209 // } 210 // 211 // // ... and use it via: 212 // db.Exec(..., stringedID(id)) 213 func (id ID) Value() (driver.Value, error) { 214 return id.MarshalBinary() 215 } 216 217 // Scan implements the sql.Scanner interface by attempting to convert the given value 218 // into an ID. 219 // 220 // When given a byte slice: 221 // - with a length of SizeBinary (10), its contents will be copied into ID. 222 // - with a length of 0, ID will be set to a zero ID. 223 // - with any other length, sets ID to a zero ID and returns InvalidDataSizeError. 224 // 225 // When given a string: 226 // - with a length of SizeEncoded (16), its contents will be decoded into ID. 227 // - with a length of 0, ID will be set to a zero ID. 228 // - with any other length, sets ID to a zero ID and returns InvalidDataSizeError. 229 // 230 // When given nil, ID will be set to a zero ID. 231 // 232 // When given any other type, returns a InvalidTypeError. 233 func (id *ID) Scan(value interface{}) error { 234 switch v := value.(type) { 235 case []byte: 236 switch len(v) { 237 case SizeBinary: 238 copy(id[:], v) 239 case 0: 240 *id = zero 241 default: 242 *id = zero 243 return &InvalidDataSizeError{Size: len(v)} 244 } 245 246 case string: 247 switch len(v) { 248 case SizeEncoded: 249 *id = internal.Decode(*(*[]byte)(unsafe.Pointer(&v))) 250 case 0: 251 *id = zero 252 default: 253 *id = zero 254 return &InvalidDataSizeError{Size: len(v)} 255 } 256 257 case nil: 258 *id = zero 259 260 default: 261 return &InvalidTypeError{Value: value} 262 } 263 264 return nil 265 }