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