llkv_types/ids.rs
1//! Identifiers shared across LLKV crates.
2//!
3//! These types live in `llkv-types` so they can be reused without depending on
4//! the storage-specific crates.
5
6// FIXME: Since upgrading to `rustc 1.90.0 (1159e78c4 2025-09-14)`, this seems
7// to be needed to workaround parenthesis errors in `LogicalFieldId`, which
8// creep up regardless of comments being added or not. This is possibly a bug
9// with Clippy or `modular_bitfield`, or a small incompatibility issue.
10#![allow(unused_parens)]
11
12use modular_bitfield::prelude::*;
13
14#[inline]
15fn rowid_shadow(fid: LogicalFieldId) -> LogicalFieldId {
16 fid.with_namespace(LogicalStorageNamespace::RowIdShadow)
17}
18
19/// Category of data a column contains.
20///
21/// The `LogicalStorageNamespace` enum prevents ID collisions by segregating different types of
22/// columns into distinct namespaces. Each namespace can contain up to 2^16 tables,
23/// and each table can have up to 2^32 fields.
24///
25/// # Usage
26///
27/// Namespaces are embedded in [`LogicalFieldId`] to create globally unique column
28/// identifiers. User code typically works with `UserData` columns, while system
29/// components use the other namespaces for internal bookkeeping.
30#[derive(Specifier, Debug, PartialEq, Eq, Clone, Copy)]
31#[bits = 16]
32pub enum LogicalStorageNamespace {
33 /// User-defined table columns.
34 ///
35 /// This is the default namespace for regular table columns. When a table is created
36 /// with columns like `name TEXT, age INT`, those columns use the `UserData` namespace.
37 UserData = 0,
38
39 /// Internal shadow column tracking row IDs.
40 ///
41 /// For each user column, the storage engine maintains a corresponding shadow column
42 /// that stores the row ID for each value. This enables efficient row-level operations
43 /// and join/filter optimizations.
44 RowIdShadow = 1,
45
46 /// MVCC metadata: transaction that created each row.
47 ///
48 /// Stores the transaction ID (`TxnId`) that inserted each row. Used for snapshot
49 /// isolation to determine row visibility.
50 TxnCreatedBy = 2,
51
52 /// MVCC metadata: transaction that deleted each row.
53 ///
54 /// Stores the transaction ID that deleted each row, or `TXN_ID_NONE` if the row
55 /// is not deleted. Used for snapshot isolation and garbage collection.
56 TxnDeletedBy = 3,
57
58 /// Reserved for future system use.
59 ///
60 /// The value `0xFFFF` is reserved as a sentinel to allow future expansion without
61 /// breaking compatibility.
62 Reserved = 0xFFFF,
63}
64
65/// Unique identifier for a table.
66///
67/// Table IDs are 16-bit unsigned integers, allowing up to 65,535 tables per database.
68/// This type is embedded in [`LogicalFieldId`] to associate columns with tables.
69///
70/// # Special Values
71///
72/// - Table ID `0` is reserved for the system catalog
73/// - User tables receive IDs starting from `1`
74pub type TableId = u16;
75
76/// Unique identifier for a column within a table.
77///
78/// Field IDs are 32-bit unsigned integers, allowing up to ~4.3 billion columns per table.
79/// This type is stored in [`LogicalFieldId::field_id`] and must match that bitfield width.
80///
81/// # Special Values
82///
83/// - Field ID `0` (`ROW_ID_FIELD_ID`) is reserved for row ID columns
84/// - Field ID `u32::MAX` is reserved for MVCC `created_by` columns
85/// - Field ID `u32::MAX - 1` is reserved for MVCC `deleted_by` columns
86/// - User columns receive IDs starting from `1`
87pub type FieldId = u32;
88
89/// Reserved field ID for row ID columns.
90///
91/// This constant is used for the synthetic row ID column that exists in all tables.
92/// Row IDs are globally unique `u64` values that never change once assigned.
93pub const ROW_ID_FIELD_ID: FieldId = 0;
94
95/// Unique identifier for a row within a table.
96///
97/// Row IDs are 64-bit unsigned integers assigned sequentially on insert. They are:
98/// - Globally unique within a table
99/// - Never reused (even after deletion)
100/// - Monotonically increasing (within append batches)
101/// - Used for joins, filters, and row-level operations
102pub type RowId = u64;
103
104/// Globally unique identifier for a column in the storage engine.
105///
106/// A `LogicalFieldId` combines three components into a single 64-bit value:
107/// - **Namespace** (16 bits): Category of data (user, system, MVCC)
108/// - **Table ID** (16 bits): Which table the column belongs to
109/// - **Field ID** (32 bits): Which column within the table
110///
111/// This design prevents ID collisions across different tables and data categories while
112/// keeping identifiers compact and easy to pass around.
113///
114/// # Bit Layout
115///
116/// ```text
117/// |-------- 64 bits total --------|
118/// | namespace | table_id | field_id |
119/// | 16 bits | 16 bits | 32 bits |
120/// ```
121///
122/// # Construction
123///
124/// Use the constructor methods rather than directly manipulating bits:
125/// - [`LogicalFieldId::for_user`] - User-defined columns
126/// - [`LogicalFieldId::for_mvcc_created_by`] - MVCC created_by metadata
127/// - [`LogicalFieldId::for_mvcc_deleted_by`] - MVCC deleted_by metadata
128/// - [`LogicalFieldId::from_parts`] - Custom construction
129///
130/// # Thread Safety
131///
132/// `LogicalFieldId` is `Copy` and thread-safe.
133#[bitfield]
134#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
135#[repr(u64)]
136pub struct LogicalFieldId {
137 /// Column identifier within the table (32 bits).
138 ///
139 /// Supports up to ~4.3 billion columns per table. Field ID `0` is reserved for
140 /// row ID columns.
141 pub field_id: B32,
142
143 /// Table identifier (16 bits).
144 ///
145 /// Supports up to 65,535 tables. Table ID `0` is reserved for the system catalog.
146 pub table_id: B16,
147
148 /// Data category (16 bits).
149 ///
150 /// Determines whether this ID refers to user data, system metadata, or MVCC tracking.
151 pub namespace: LogicalStorageNamespace,
152}
153
154impl LogicalFieldId {
155 /// Construct a `LogicalFieldId` from individual components.
156 ///
157 /// This is the most general constructor. Use the convenience methods
158 /// ([`for_user`](Self::for_user), [`for_mvcc_created_by`](Self::for_mvcc_created_by), etc.)
159 /// for common cases.
160 #[inline]
161 pub fn from_parts(
162 namespace: LogicalStorageNamespace,
163 table_id: TableId,
164 field_id: FieldId,
165 ) -> Self {
166 LogicalFieldId::new()
167 .with_namespace(namespace)
168 .with_table_id(table_id)
169 .with_field_id(field_id)
170 }
171
172 /// Create an ID for a user-defined column.
173 ///
174 /// This is the most common constructor for regular table columns. It uses the
175 /// `UserData` namespace.
176 #[inline]
177 pub fn for_user(table_id: TableId, field_id: FieldId) -> Self {
178 Self::from_parts(LogicalStorageNamespace::UserData, table_id, field_id)
179 }
180
181 /// Create an ID for a user column in table 0.
182 ///
183 /// This is a convenience method for tests and examples that use the default table ID.
184 #[inline]
185 pub fn for_user_table_0(field_id: FieldId) -> Self {
186 Self::for_user(0, field_id)
187 }
188
189 /// Create an ID for the MVCC `created_by` column of a table.
190 ///
191 /// Each table has a `created_by` column that tracks which transaction inserted
192 /// each row. The field ID is always `u32::MAX` as a sentinel value.
193 #[inline]
194 pub fn for_mvcc_created_by(table_id: TableId) -> Self {
195 Self::from_parts(LogicalStorageNamespace::TxnCreatedBy, table_id, u32::MAX)
196 }
197
198 /// Create an ID for the MVCC `deleted_by` column of a table.
199 ///
200 /// Each table has a `deleted_by` column that tracks which transaction deleted
201 /// each row (or `TXN_ID_NONE` if not deleted). The field ID is always `u32::MAX - 1`
202 /// as a sentinel value.
203 #[inline]
204 pub fn for_mvcc_deleted_by(table_id: TableId) -> Self {
205 Self::from_parts(
206 LogicalStorageNamespace::TxnDeletedBy,
207 table_id,
208 u32::MAX - 1,
209 )
210 }
211}
212
213/// Convenience helper for constructing a user-space logical field id.
214#[inline]
215pub fn lfid(table_id: TableId, col_id: FieldId) -> LogicalFieldId {
216 LogicalFieldId::for_user(table_id, col_id)
217}
218
219/// Logical field id for the table's row id shadow column, as a `u64` key.
220#[inline]
221pub fn rid_col(table_id: TableId, col_id: FieldId) -> u64 {
222 let fid = lfid(table_id, col_id);
223 rowid_shadow(fid).into()
224}
225
226/// Logical field id for the reserved table row id column, as a `u64` key.
227#[inline]
228pub fn rid_table(table_id: TableId) -> u64 {
229 lfid(table_id, ROW_ID_FIELD_ID).into()
230}