lexkey

package module
v0.1.0-alpha.35 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2025 License: MIT Imports: 8 Imported by: 22

README

ci Dependabot Updates

lexkey

lexkey is a lightweight lexicographically sortable key encoding library for Go. It provides consistent, ordered, and efficient encoding for common data types so they sort correctly in byte-wise order.

Features

  • 🚀 Lexicographically sortable encoding for structured keys.
  • 🔑 Supports strings, integers, floats, UUIDs, booleans, timestamps, durations, and byte slices.
  • 🔄 Consistent ordering for mixed types like int32 and int64.
  • 📦 Optimized encoding for space efficiency and speed.
  • 📡 JSON serialization support for interoperability.

📦 Installation

go get github.com/fgrzl/lexkey

🛠 Usage

Create a LexKey
key := lexkey.Encode("user", 123, true)
fmt.Println("Encoded Key (Hex):", key.ToHexString())
Sorting Keys
key1 := lexkey.Encode("apple")
key2 := lexkey.Encode("banana")

fmt.Println(string(key1) < string(key2)) // ✅ True (correct lexicographic order)
Handling Numbers
key1 := lexkey.Encode(int64(-100))
key2 := lexkey.Encode(int64(50))

fmt.Println(string(key1) < string(key2)) // ✅ True (correct sorting for signed integers)
Using UUIDs
import "github.com/google/uuid"

id := uuid.New()
key := lexkey.Encode("order", id)

fmt.Println("Encoded UUID Key:", key.ToHexString())
LexKey JSON Serialization
import "encoding/json"

key := lexkey.Encode("session", 42)
jsonData, _ := json.Marshal(key)
fmt.Println(string(jsonData)) // ✅ Encoded as a hex string

🔍 Supported Data Types

Type Supported? Encoding Details
string ✅ Yes Stored as raw UTF-8 bytes
int32 ✅ Yes Canonicalized to int64 for uniform sorting
int64 ✅ Yes Sign-bit flipped for correct ordering
uint32 ✅ Yes Big-endian encoded
uint64 ✅ Yes Big-endian encoded
float32 ✅ Yes Canonicalized to float64 then transformed
float64 ✅ Yes IEEE 754 encoded with sign-bit transformation
bool ✅ Yes true → 0x01, false → 0x00
uuid.UUID ✅ Yes 16-byte raw representation
[]byte ✅ Yes Stored as-is
time.Time ✅ Yes Encoded as int64 nanoseconds since Unix epoch
time.Duration ✅ Yes Encoded as int64 nanoseconds

📌 Key Functions

Encoding Keys
func Encode(parts ...any) LexKey
func NewLexKey(parts ...any) (LexKey, error)
func EncodeInto(dst []byte, parts ...any) (int, error)
func EncodeSize(parts ...any) int

Notes:

  • As of 2025-10-01, numeric width canonicalization is the default (breaking change):
    • int, int8, int16, int32 → int64
    • uint8, uint16, uint32 → uint64
    • float32 → float64
  • For explicit use, the following helpers are provided (equivalent to default behavior):
    • EncodeCanonicalWidth / NewLexKeyCanonicalWidth / EncodeIntoCanonicalWidth / EncodeSizeCanonicalWidth
Sorting Helpers
func EncodeFirst(parts ...any) LexKey // lower bound: prefix + 0x00 (sorts before any extension of the prefix)
func EncodeLast(parts ...any) LexKey  // upper bound: prefix + 0xFF (sorts after any extension of the prefix)
func Compare(a, b LexKey) int         // -1/0/1 without allocations

Prefix scans:

// To scan all keys with a given prefix, use a half-open range [lower, upper):
lower := lexkey.EncodeFirst("tenant", "users") // ... 00
upper := lexkey.EncodeLast("tenant", "users")  // ... ff
// All keys that start with ("tenant", "users", ...) will satisfy: lower <= key && key < upper
Hex Encoding
func (e LexKey) ToHexString() string
func (e *LexKey) FromHexString(hexStr string) error
JSON Serialization
func (e LexKey) MarshalJSON() ([]byte, error)
func (e *LexKey) UnmarshalJSON(data []byte) error

🏆 Why Use lexkey?

Fast & Efficient → Uses compact, binary-safe encoding.
Correct Ordering → Works across all supported types.
Minimal Dependencies → Only uuid and standard Go packages.

🛠 Testing

Run the full test suite:

go test -cover ./...

🔄 Breaking change (2025-10-01)

Numeric width canonicalization is now the default. This ensures logical numeric ordering across widths (e.g., uint32(1) and uint64(1) are equal on the wire). If you require the previous native-width bytes, pin an older release or re-encode using legacy rules as described in SPEC.md.

See SPEC.md for the full encoding specification and test vectors.

Test Coverage:99.6% 🎯

Documentation

Index

Constants

View Source
const (
	Seperator = 0x00 // Separates parts within a LexKey
	EndMarker = 0xFF // Marks the end of a range for lexicographic sorting
)

Special bytes used in lexicographic encoding

Variables

View Source
var (
	Empty = LexKey{}
	Last  = Encode(EndMarker)
)

Functions

func Compare

func Compare(a, b LexKey) int

Compare returns -1, 0, 1 for a < b, a == b, a > b respectively without allocations.

func EncodeInto

func EncodeInto(dst []byte, parts ...any) (int, error)

EncodeInto writes the encoding of parts into dst and returns the number of bytes written. The dst slice must have length >= EncodeSize(parts...). No allocations are performed.

func EncodeIntoCanonicalWidth

func EncodeIntoCanonicalWidth(dst []byte, parts ...any) (int, error)

EncodeIntoCanonicalWidth writes the canonical-width encoding into dst.

func EncodeSize

func EncodeSize(parts ...any) int

EncodeSize returns the exact number of bytes required to encode the given parts, including separators. Use this to pre-allocate a destination buffer for EncodeInto.

func EncodeSizeCanonicalWidth

func EncodeSizeCanonicalWidth(parts ...any) int

EncodeSizeCanonicalWidth returns the size after width canonicalization.

Types

type LexKey

type LexKey []byte

LexKey represents an encoded key as a byte slice, optimized for lexicographic sorting. An empty LexKey (length 0) is valid and distinct from nil.

func Encode

func Encode(parts ...any) LexKey

Encode constructs a LexKey from pre-validated parts, panicking if encoding fails. Use this when inputs are guaranteed to be valid (e.g., no unsupported types). For fallible construction, use NewLexKey instead.

func EncodeCanonicalWidth

func EncodeCanonicalWidth(parts ...any) LexKey

EncodeCanonicalWidth panics on error; see NewLexKeyCanonicalWidth.

func EncodeFirst

func EncodeFirst(parts ...any) LexKey

EncodeFirst returns the first lexicographically sortable key in a range. Adds a Seperator byte to the prefix to ensure it sorts before any extension.

Note: this calls Encode and will panic on encoding errors (i.e., when given unsupported types).

func EncodeLast

func EncodeLast(parts ...any) LexKey

EncodeLast returns the last lexicographically sortable key in a range. Adds an EndMarker byte to the prefix to ensure it sorts after any extension.

Note: this calls Encode and will panic on encoding errors (i.e., when given unsupported types).

func NewLexKey

func NewLexKey(parts ...any) (LexKey, error)

NewLexKey constructs a LexKey from a list of parts, ensuring lexicographic sorting. Returns an error if parts is empty or contains unsupported types. The resulting key is a concatenation of encoded parts separated by Seperator bytes.

func NewLexKeyCanonicalWidth

func NewLexKeyCanonicalWidth(parts ...any) (LexKey, error)

NewLexKeyCanonicalWidth constructs a LexKey after normalizing numeric widths so cross-width numeric values sort logically. Backwards-compatible: bytes differ from NewLexKey because widths are canonicalized; use only if you control both sides.

func (*LexKey) FromHexString

func (e *LexKey) FromHexString(hexStr string) error

FromHexString decodes a hexadecimal string back into a LexKey. Sets to an empty slice (not nil) for an empty input string. Returns an error if the hex string is invalid.

func (LexKey) IsEmpty

func (e LexKey) IsEmpty() bool

IsEmpty checks if the LexKey is empty (length 0). A nil LexKey is considered empty.

func (LexKey) MarshalJSON

func (e LexKey) MarshalJSON() ([]byte, error)

MarshalJSON encodes LexKey as a hex string for JSON serialization.

func (LexKey) MarshalText

func (e LexKey) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler, returning a hex encoding of the key.

func (LexKey) ToHexString

func (e LexKey) ToHexString() string

ToHexString converts the LexKey to a hexadecimal string. Returns an empty string for an empty or nil LexKey.

func (*LexKey) UnmarshalJSON

func (e *LexKey) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a hex string from JSON into a LexKey. Handles JSON null by setting to an empty slice.

func (*LexKey) UnmarshalText

func (e *LexKey) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler, decoding a hex input into the key.

type PrimaryKey

type PrimaryKey struct {
	PartitionKey LexKey
	RowKey       LexKey
}

PrimaryKey represents a composite key for key-value storage.

func DecodePrimaryKey

func DecodePrimaryKey(raw []byte) (PrimaryKey, error)

DecodePrimaryKey decodes a PrimaryKey from its byte encoding. Returns an error if the separator is missing or input is invalid.

func NewPrimaryKey

func NewPrimaryKey(partitionKey, rowKey LexKey) PrimaryKey

NewPrimaryKey creates a new PrimaryKey from partition and row keys. Panics if either key is nil.

func (PrimaryKey) Encode

func (pk PrimaryKey) Encode() LexKey

Encode concatenates PartitionKey and RowKey with a Seperator.

type RangeKey

type RangeKey struct {
	PartitionKey LexKey
	StartRowKey  LexKey
	EndRowKey    LexKey
}

RangeKey defines a range query over keys.

func NewRangeKey

func NewRangeKey(partition, lower, upper LexKey) RangeKey

NewRangeKey creates a RangeKey for a given partition and row key range. Panics if the partition key, lower, or upper key is nil.

func NewRangeKeyFull

func NewRangeKeyFull(partition LexKey) RangeKey

NewRangeKeyFull creates a RangeKey spanning the full partition. Panics if the partition key is nil.

func (RangeKey) Encode

func (rk RangeKey) Encode(withPartitionKey bool) (lower, upper LexKey)

Encode encodes the range boundaries for range queries.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL