Encodes and decodes compound keys for data structure storage in Bitcask.
FerricStore stores data structure sub-elements (hash fields, list positions, set members, sorted set entries) as individual Bitcask entries using compound keys. This avoids the need for per-key Bitcask instances while still allowing individual field/member access without reading entire structures.
Key Format
Each compound key has the format:
PREFIX:redis_key\0sub_keyWhere:
PREFIXis a single uppercase letter identifying the data type:Hfor Hash fieldsLfor List elements (sub_key is a float64 position)Sfor Set membersZfor Sorted Set entriesTfor Type metadata (no sub_key)
redis_keyis the user-facing Redis key name\0(null byte) is the separator between the Redis key and sub-keysub_keyis the type-specific sub-key (field name, position, member, etc.)
The null byte separator is safe because Redis keys in normal use do not contain null bytes (and the RESP protocol does not transmit them within key strings in standard client libraries).
Type Metadata
A type index entry T:keyname stores the type as a simple string value
("hash", "list", "set", "zset"). This is used by the TYPE command
and for WRONGTYPE enforcement -- attempting to use hash commands on a set
key returns an error.
Summary
Functions
Decodes a position string back to a float64.
Decodes a stored type string back to the data type atom.
Encodes a float64 position as a fixed-width string that sorts lexicographically in the same order as the numeric values.
Encodes a data type atom to its stored string representation.
Extracts the Redis-level key from a compound key.
Extracts the sub-key portion from a compound key given a known prefix.
Builds a compound key for a hash field.
Returns the scan prefix for all fields of a hash.
Returns true if the given Bitcask key is an internal compound key
(starts with a known type prefix like H:, L:, S:, Z:, T:).
Builds a compound key for a list element at a given float64 position.
Builds the metadata compound key for a list.
Returns the scan prefix for all elements of a list.
Builds a compound key for a set member.
Returns the scan prefix for all members of a set.
Builds the type metadata compound key for a Redis key.
Filters a list of raw ETS/store keys to only user-visible logical keys.
Builds a compound key for a sorted set member-to-score mapping.
Returns the scan prefix for all members of a sorted set.
Types
Functions
Decodes a position string back to a float64.
Examples
iex> pos = 1000.5
iex> encoded = Ferricstore.Store.CompoundKey.encode_position(pos)
iex> Ferricstore.Store.CompoundKey.decode_position(encoded)
1000.5
Decodes a stored type string back to the data type atom.
Examples
iex> Ferricstore.Store.CompoundKey.decode_type("hash")
:hash
Encodes a float64 position as a fixed-width string that sorts lexicographically in the same order as the numeric values.
Negative positions use a complement encoding so that lexicographic order is preserved.
Examples
iex> a = Ferricstore.Store.CompoundKey.encode_position(1.0)
iex> b = Ferricstore.Store.CompoundKey.encode_position(2.0)
iex> a < b
true
Encodes a data type atom to its stored string representation.
Examples
iex> Ferricstore.Store.CompoundKey.encode_type(:hash)
"hash"
Extracts the Redis-level key from a compound key.
For compound keys like S:myset\0member, returns "myset".
For type metadata keys like T:myset, returns "myset".
For plain (non-compound) keys, returns the key as-is.
Used by the cross-shard lock checking logic to determine which Redis key a compound Raft write affects.
Examples
iex> Ferricstore.Store.CompoundKey.extract_redis_key("S:myset" <> <<0>> <> "member")
"myset"
iex> Ferricstore.Store.CompoundKey.extract_redis_key("H:hash" <> <<0>> <> "field")
"hash"
iex> Ferricstore.Store.CompoundKey.extract_redis_key("T:mykey")
"mykey"
iex> Ferricstore.Store.CompoundKey.extract_redis_key("plain_key")
"plain_key"
Extracts the sub-key portion from a compound key given a known prefix.
Examples
iex> prefix = Ferricstore.Store.CompoundKey.hash_prefix("user:123")
iex> compound = Ferricstore.Store.CompoundKey.hash_field("user:123", "name")
iex> Ferricstore.Store.CompoundKey.extract_subkey(compound, prefix)
"name"
Builds a compound key for a hash field.
Examples
iex> Ferricstore.Store.CompoundKey.hash_field("user:123", "name")
<<"H:user:123", 0, "name">>
Returns the scan prefix for all fields of a hash.
Examples
iex> Ferricstore.Store.CompoundKey.hash_prefix("user:123")
<<"H:user:123", 0>>
Returns true if the given Bitcask key is an internal compound key
(starts with a known type prefix like H:, L:, S:, Z:, T:).
Used to filter internal keys out of user-facing KEYS and DBSIZE results.
Examples
iex> Ferricstore.Store.CompoundKey.internal_key?("H:user:123" <> <<0>> <> "name")
true
iex> Ferricstore.Store.CompoundKey.internal_key?("my_plain_key")
false
Builds a compound key for a list element at a given float64 position.
The position is encoded as a fixed-width, zero-padded float string to
ensure lexicographic ordering matches numeric ordering. Format is
{sign}{integer_part}.{fractional_part} with 20 digits before and 17
digits after the decimal point.
Examples
iex> key = Ferricstore.Store.CompoundKey.list_element("mylist", 1000.0)
iex> String.starts_with?(key, "L:mylist" <> <<0>>)
true
Builds the metadata compound key for a list.
Returns the scan prefix for all elements of a list.
Examples
iex> Ferricstore.Store.CompoundKey.list_prefix("mylist")
<<"L:mylist", 0>>
Builds a compound key for a set member.
The member name IS the sub-key; the value stored is "1" (presence marker).
Examples
iex> Ferricstore.Store.CompoundKey.set_member("tags:post:789", "elixir")
<<"S:tags:post:789", 0, "elixir">>
Returns the scan prefix for all members of a set.
Examples
iex> Ferricstore.Store.CompoundKey.set_prefix("tags:post:789")
<<"S:tags:post:789", 0>>
Builds the type metadata compound key for a Redis key.
Examples
iex> Ferricstore.Store.CompoundKey.type_key("user:123")
"T:user:123"
Filters a list of raw ETS/store keys to only user-visible logical keys.
- Rejects all internal compound keys (
H:,L:,S:,Z:,V:,VM:,PM:,LM:) - Extracts logical keys from type registry entries (
T:key->key) - Keeps plain string keys as-is
- Deduplicates (a key may exist both as a plain entry and a
T:entry)
Builds a compound key for a sorted set member-to-score mapping.
Stores member -> score for O(1) score lookups by member.
Examples
iex> Ferricstore.Store.CompoundKey.zset_member("leaderboard", "alice")
<<"Z:leaderboard", 0, "alice">>
Returns the scan prefix for all members of a sorted set.
Examples
iex> Ferricstore.Store.CompoundKey.zset_prefix("leaderboard")
<<"Z:leaderboard", 0>>