Skip to main content

reifydb_core/interface/catalog/
series.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_type::value::{Value, datetime::DateTime, sumtype::SumTypeId, r#type::Type};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8	interface::catalog::{
9		column::Column,
10		id::{NamespaceId, SeriesId},
11		key::PrimaryKey,
12	},
13	value::column::data::ColumnData,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
17#[serde(rename_all = "lowercase")]
18#[derive(Default)]
19pub enum TimestampPrecision {
20	#[default]
21	Millisecond = 0,
22	Microsecond = 1,
23	Nanosecond = 2,
24	Second = 3,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub enum SeriesKey {
29	DateTime {
30		column: String,
31		precision: TimestampPrecision,
32	},
33	Integer {
34		column: String,
35	},
36}
37
38impl SeriesKey {
39	pub fn column(&self) -> &str {
40		match self {
41			SeriesKey::DateTime {
42				column,
43				..
44			} => column,
45			SeriesKey::Integer {
46				column,
47			} => column,
48		}
49	}
50
51	/// Decode a `SeriesKey` from its stored representation.
52	///
53	/// `key_kind`: 1 = Integer, otherwise DateTime.
54	/// `precision_raw`: only used for DateTime keys (0=ms, 1=us, 2=ns, 3=s).
55	pub fn decode(key_kind: u8, precision_raw: u8, column: String) -> Self {
56		match key_kind {
57			1 => SeriesKey::Integer {
58				column,
59			},
60			_ => {
61				let precision = match precision_raw {
62					1 => TimestampPrecision::Microsecond,
63					2 => TimestampPrecision::Nanosecond,
64					3 => TimestampPrecision::Second,
65					_ => TimestampPrecision::Millisecond,
66				};
67				SeriesKey::DateTime {
68					column,
69					precision,
70				}
71			}
72		}
73	}
74}
75
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct Series {
78	pub id: SeriesId,
79	pub namespace: NamespaceId,
80	pub name: String,
81	pub columns: Vec<Column>,
82	pub tag: Option<SumTypeId>,
83	pub key: SeriesKey,
84	pub primary_key: Option<PrimaryKey>,
85}
86
87impl Series {
88	pub fn name(&self) -> &str {
89		&self.name
90	}
91
92	/// Returns the Type of the key column, if the key column is found in columns.
93	pub fn key_column_type(&self) -> Option<Type> {
94		let key_col_name = self.key.column();
95		self.columns.iter().find(|c| c.name == key_col_name).map(|c| c.constraint.get_type())
96	}
97
98	/// Convert a Value to u64 for series key encoding.
99	///
100	/// Handles both integer and datetime key types. For datetime keys, the nanos-since-epoch
101	/// value is divided by the precision factor to recover the original u64 key.
102	/// Negative values (pre-1970 dates, negative integers) are rejected.
103	pub fn key_to_u64(&self, value: Value) -> Option<u64> {
104		match value {
105			Value::Int1(v) => u64::try_from(v).ok(),
106			Value::Int2(v) => u64::try_from(v).ok(),
107			Value::Int4(v) => u64::try_from(v).ok(),
108			Value::Int8(v) => u64::try_from(v).ok(),
109			Value::Int16(v) => u64::try_from(v).ok(),
110			Value::Uint1(v) => Some(v as u64),
111			Value::Uint2(v) => Some(v as u64),
112			Value::Uint4(v) => Some(v as u64),
113			Value::Uint8(v) => Some(v),
114			Value::Uint16(v) => u64::try_from(v).ok(),
115			Value::DateTime(dt) => {
116				let nanos = dt.to_nanos();
117				match &self.key {
118					SeriesKey::DateTime {
119						precision,
120						..
121					} => Some(match precision {
122						TimestampPrecision::Second => nanos / 1_000_000_000,
123						TimestampPrecision::Millisecond => nanos / 1_000_000,
124						TimestampPrecision::Microsecond => nanos / 1_000,
125						TimestampPrecision::Nanosecond => nanos,
126					}),
127					_ => Some(nanos),
128				}
129			}
130			_ => None,
131		}
132	}
133
134	/// Convert a u64 key value back to the appropriate Value type for encoding.
135	pub fn key_from_u64(&self, v: u64) -> Value {
136		let ty = self.key_column_type();
137		match ty.as_ref() {
138			Some(Type::Int1) => Value::Int1(v as i8),
139			Some(Type::Int2) => Value::Int2(v as i16),
140			Some(Type::Int4) => Value::Int4(v as i32),
141			Some(Type::Int8) => Value::Int8(v as i64),
142			Some(Type::Uint1) => Value::Uint1(v as u8),
143			Some(Type::Uint2) => Value::Uint2(v as u16),
144			Some(Type::Uint4) => Value::Uint4(v as u32),
145			Some(Type::Uint8) => Value::Uint8(v),
146			Some(Type::Uint16) => Value::Uint16(v as u128),
147			Some(Type::Int16) => Value::Int16(v as i128),
148			Some(Type::DateTime) => {
149				let nanos: u64 = match &self.key {
150					SeriesKey::DateTime {
151						precision,
152						..
153					} => match precision {
154						TimestampPrecision::Second => v * 1_000_000_000,
155						TimestampPrecision::Millisecond => v * 1_000_000,
156						TimestampPrecision::Microsecond => v * 1_000,
157						TimestampPrecision::Nanosecond => v,
158					},
159					_ => v,
160				};
161				Value::DateTime(DateTime::from_nanos(nanos))
162			}
163			_ => Value::Uint8(v),
164		}
165	}
166
167	/// Build a ColumnData from u64 key values using the proper key column type.
168	pub fn key_column_data(&self, keys: Vec<u64>) -> ColumnData {
169		let key_type = self.key_column_type();
170		match &key_type {
171			Some(ty) => {
172				let mut data = ColumnData::with_capacity(ty.clone(), keys.len());
173				for k in keys {
174					data.push_value(self.key_from_u64(k));
175				}
176				data
177			}
178			None => ColumnData::uint8(keys),
179		}
180	}
181
182	/// Returns columns excluding the key column (data columns only).
183	pub fn data_columns(&self) -> impl Iterator<Item = &Column> {
184		let key_column = self.key.column().to_string();
185		self.columns.iter().filter(move |c| c.name != key_column)
186	}
187}
188
189#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190pub struct SeriesMetadata {
191	pub id: SeriesId,
192	pub row_count: u64,
193	pub oldest_key: u64,
194	pub newest_key: u64,
195	pub sequence_counter: u64,
196}
197
198impl SeriesMetadata {
199	pub fn new(series_id: SeriesId) -> Self {
200		Self {
201			id: series_id,
202			row_count: 0,
203			oldest_key: 0,
204			newest_key: 0,
205			sequence_counter: 0,
206		}
207	}
208}