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  }