Skip to main content

reifydb_core/interface/catalog/
object.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::fmt;
5
6use reifydb_value::value::dictionary::DictionaryId;
7use serde::{Deserialize, Serialize};
8
9use crate::interface::catalog::{
10	id::{QueueId, RingBufferId, SeriesId, TableId, ViewId},
11	table::Table,
12	view::View,
13	vtable::{VTable, VTableId},
14};
15
16#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Serialize, Deserialize)]
17pub enum ObjectId {
18	Table(TableId),
19	View(ViewId),
20	TableVirtual(VTableId),
21	RingBuffer(RingBufferId),
22	Dictionary(DictionaryId),
23	Series(SeriesId),
24	Queue(QueueId),
25}
26
27impl fmt::Display for ObjectId {
28	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29		match self {
30			ObjectId::Table(id) => write!(f, "{}", id.0),
31			ObjectId::View(id) => write!(f, "{}", id.0),
32			ObjectId::TableVirtual(id) => write!(f, "{}", id.0),
33			ObjectId::RingBuffer(id) => write!(f, "{}", id.0),
34			ObjectId::Dictionary(id) => write!(f, "{}", id.0),
35			ObjectId::Series(id) => write!(f, "{}", id.0),
36			ObjectId::Queue(id) => write!(f, "{}", id.0),
37		}
38	}
39}
40
41impl ObjectId {
42	pub fn table(id: impl Into<TableId>) -> Self {
43		Self::Table(id.into())
44	}
45
46	pub fn view(id: impl Into<ViewId>) -> Self {
47		Self::View(id.into())
48	}
49
50	pub fn vtable(id: impl Into<VTableId>) -> Self {
51		Self::TableVirtual(id.into())
52	}
53
54	pub fn ringbuffer(id: impl Into<RingBufferId>) -> Self {
55		Self::RingBuffer(id.into())
56	}
57
58	pub fn dictionary(id: impl Into<DictionaryId>) -> Self {
59		Self::Dictionary(id.into())
60	}
61
62	pub fn series(id: impl Into<SeriesId>) -> Self {
63		Self::Series(id.into())
64	}
65
66	pub fn queue(id: impl Into<QueueId>) -> Self {
67		Self::Queue(id.into())
68	}
69
70	pub fn to_u64(self) -> u64 {
71		match self {
72			ObjectId::Table(id) => id.0,
73			ObjectId::View(id) => id.0,
74			ObjectId::TableVirtual(id) => id.0,
75			ObjectId::RingBuffer(id) => id.0,
76			ObjectId::Dictionary(id) => id.0,
77			ObjectId::Series(id) => id.0,
78			ObjectId::Queue(id) => id.0,
79		}
80	}
81}
82
83impl From<TableId> for ObjectId {
84	fn from(id: TableId) -> Self {
85		ObjectId::Table(id)
86	}
87}
88
89impl From<ViewId> for ObjectId {
90	fn from(id: ViewId) -> Self {
91		ObjectId::View(id)
92	}
93}
94
95impl From<VTableId> for ObjectId {
96	fn from(id: VTableId) -> Self {
97		ObjectId::TableVirtual(id)
98	}
99}
100
101impl From<RingBufferId> for ObjectId {
102	fn from(id: RingBufferId) -> Self {
103		ObjectId::RingBuffer(id)
104	}
105}
106
107impl From<DictionaryId> for ObjectId {
108	fn from(id: DictionaryId) -> Self {
109		ObjectId::Dictionary(id)
110	}
111}
112
113impl From<SeriesId> for ObjectId {
114	fn from(id: SeriesId) -> Self {
115		ObjectId::Series(id)
116	}
117}
118
119impl From<QueueId> for ObjectId {
120	fn from(id: QueueId) -> Self {
121		ObjectId::Queue(id)
122	}
123}
124
125impl PartialEq<u64> for ObjectId {
126	fn eq(&self, other: &u64) -> bool {
127		match self {
128			ObjectId::Table(id) => id.0.eq(other),
129			ObjectId::View(id) => id.0.eq(other),
130			ObjectId::TableVirtual(id) => id.0.eq(other),
131			ObjectId::RingBuffer(id) => id.0.eq(other),
132			ObjectId::Dictionary(id) => id.0.eq(other),
133			ObjectId::Series(id) => id.0.eq(other),
134			ObjectId::Queue(id) => id.0.eq(other),
135		}
136	}
137}
138
139impl PartialEq<TableId> for ObjectId {
140	fn eq(&self, other: &TableId) -> bool {
141		match self {
142			ObjectId::Table(id) => id.0 == other.0,
143			_ => false,
144		}
145	}
146}
147
148impl PartialEq<ViewId> for ObjectId {
149	fn eq(&self, other: &ViewId) -> bool {
150		match self {
151			ObjectId::View(id) => id.0 == other.0,
152			_ => false,
153		}
154	}
155}
156
157impl PartialEq<VTableId> for ObjectId {
158	fn eq(&self, other: &VTableId) -> bool {
159		match self {
160			ObjectId::TableVirtual(id) => id.0 == other.0,
161			_ => false,
162		}
163	}
164}
165
166impl PartialEq<RingBufferId> for ObjectId {
167	fn eq(&self, other: &RingBufferId) -> bool {
168		match self {
169			ObjectId::RingBuffer(id) => id.0 == other.0,
170			_ => false,
171		}
172	}
173}
174
175impl PartialEq<DictionaryId> for ObjectId {
176	fn eq(&self, other: &DictionaryId) -> bool {
177		match self {
178			ObjectId::Dictionary(id) => id.0 == other.0,
179			_ => false,
180		}
181	}
182}
183
184impl PartialEq<SeriesId> for ObjectId {
185	fn eq(&self, other: &SeriesId) -> bool {
186		match self {
187			ObjectId::Series(id) => id.0 == other.0,
188			_ => false,
189		}
190	}
191}
192
193impl From<ObjectId> for u64 {
194	fn from(object: ObjectId) -> u64 {
195		object.as_u64()
196	}
197}
198
199impl ObjectId {
200	pub fn type_tag(&self) -> u8 {
201		match self {
202			ObjectId::Table(_) => 0x01,
203			ObjectId::View(_) => 0x02,
204			ObjectId::TableVirtual(_) => 0x03,
205			ObjectId::RingBuffer(_) => 0x04,
206			ObjectId::Dictionary(_) => 0x05,
207			ObjectId::Series(_) => 0x06,
208			ObjectId::Queue(_) => 0x07,
209		}
210	}
211
212	pub fn from_type_tag(tag: u8, id: u64) -> Option<Self> {
213		Some(match tag {
214			0x01 => ObjectId::Table(TableId(id)),
215			0x02 => ObjectId::View(ViewId(id)),
216			0x03 => ObjectId::TableVirtual(VTableId(id)),
217			0x04 => ObjectId::RingBuffer(RingBufferId(id)),
218			0x05 => ObjectId::Dictionary(DictionaryId(id)),
219			0x06 => ObjectId::Series(SeriesId(id)),
220			0x07 => ObjectId::Queue(QueueId(id)),
221			_ => return None,
222		})
223	}
224
225	pub fn as_u64(&self) -> u64 {
226		match self {
227			ObjectId::Table(id) => id.0,
228			ObjectId::View(id) => id.0,
229			ObjectId::TableVirtual(id) => id.0,
230			ObjectId::RingBuffer(id) => id.0,
231			ObjectId::Dictionary(id) => id.0,
232			ObjectId::Series(id) => id.0,
233			ObjectId::Queue(id) => id.0,
234		}
235	}
236
237	pub fn next(&self) -> ObjectId {
238		match self {
239			ObjectId::Table(table) => ObjectId::table(table.0 + 1),
240			ObjectId::View(view) => ObjectId::view(view.0 + 1),
241			ObjectId::TableVirtual(vtable) => ObjectId::vtable(vtable.0 + 1),
242			ObjectId::RingBuffer(ringbuffer) => ObjectId::ringbuffer(ringbuffer.0 + 1),
243			ObjectId::Dictionary(dictionary) => ObjectId::dictionary(dictionary.0 + 1),
244			ObjectId::Series(series) => ObjectId::series(series.0 + 1),
245			ObjectId::Queue(queue) => ObjectId::queue(queue.0 + 1),
246		}
247	}
248
249	pub fn prev(&self) -> ObjectId {
250		match self {
251			ObjectId::Table(table) => ObjectId::table(table.0.wrapping_sub(1)),
252			ObjectId::View(view) => ObjectId::view(view.0.wrapping_sub(1)),
253			ObjectId::TableVirtual(vtable) => ObjectId::vtable(vtable.0.wrapping_sub(1)),
254			ObjectId::RingBuffer(ringbuffer) => ObjectId::ringbuffer(ringbuffer.0.wrapping_sub(1)),
255			ObjectId::Dictionary(dictionary) => ObjectId::dictionary(dictionary.0.wrapping_sub(1)),
256			ObjectId::Series(series) => ObjectId::series(series.0.wrapping_sub(1)),
257			ObjectId::Queue(queue) => ObjectId::queue(queue.0.wrapping_sub(1)),
258		}
259	}
260}
261
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub enum Object {
264	Table(Table),
265	View(View),
266	TableVirtual(VTable),
267}
268
269impl Object {
270	pub fn id(&self) -> ObjectId {
271		match self {
272			Object::Table(table) => table.id.into(),
273			Object::View(view) => view.id().into(),
274			Object::TableVirtual(vtable) => vtable.id.into(),
275		}
276	}
277}
278
279#[cfg(test)]
280mod tests {
281	use super::*;
282
283	#[test]
284	fn the_type_tags_are_contiguous_and_pinned_to_their_on_disk_bytes() {
285		// The tag goes into the catalog and row-settings keyspaces, so it is on-disk layout: changing
286		// one orphans every row already stored under the old tag. Pinned literally rather than
287		// derived, so reordering the enum cannot move them.
288		assert_eq!(ObjectId::Table(TableId(1)).type_tag(), 0x01);
289		assert_eq!(ObjectId::View(ViewId(1)).type_tag(), 0x02);
290		assert_eq!(ObjectId::TableVirtual(VTableId(1)).type_tag(), 0x03);
291		assert_eq!(ObjectId::RingBuffer(RingBufferId(1)).type_tag(), 0x04);
292		assert_eq!(ObjectId::Dictionary(DictionaryId(1)).type_tag(), 0x05);
293		assert_eq!(ObjectId::Series(SeriesId(1)).type_tag(), 0x06);
294		assert_eq!(ObjectId::Queue(QueueId(1)).type_tag(), 0x07);
295	}
296
297	#[test]
298	fn a_tag_outside_the_assigned_range_is_rejected_rather_than_mapped_to_a_neighbour() {
299		// Accepting an out-of-range tag would let a corrupt key decode into a neighbouring kind, so a
300		// wrong-kind lookup becomes a silent miss. 0x08 and 0x00 bracket the assigned range 0x01-0x07.
301		assert_eq!(ObjectId::from_type_tag(0x00, 42), None);
302		assert_eq!(ObjectId::from_type_tag(0x08, 42), None);
303		assert_eq!(ObjectId::from_type_tag(0xff, 42), None);
304	}
305
306	#[test]
307	fn every_variant_survives_a_tag_round_trip_as_the_same_kind() {
308		// Encoders write `type_tag` and decoders read `from_type_tag`; if the two tables drift, a key
309		// decodes as a different object kind carrying the same numeric id.
310		let objects = [
311			ObjectId::Table(TableId(7)),
312			ObjectId::View(ViewId(7)),
313			ObjectId::TableVirtual(VTableId(7)),
314			ObjectId::RingBuffer(RingBufferId(7)),
315			ObjectId::Dictionary(DictionaryId(7)),
316			ObjectId::Series(SeriesId(7)),
317			ObjectId::Queue(QueueId(7)),
318		];
319
320		for object in objects {
321			assert_eq!(ObjectId::from_type_tag(object.type_tag(), object.as_u64()), Some(object));
322		}
323	}
324}