reifydb_core/interface/catalog/
series.rs1use reifydb_codec::row::pod::EncodedPodRow;
5use reifydb_value::{
6 Result,
7 value::{Value, datetime::DateTime, sumtype::SumTypeId, value_type::ValueType},
8};
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 common::TimeSource,
13 interface::catalog::{
14 column::Column,
15 id::{NamespaceId, SeriesId},
16 key::PrimaryKey,
17 },
18 return_internal_error,
19 value::column::buffer::ColumnBuffer,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "lowercase")]
24#[derive(Default)]
25pub enum TimestampPrecision {
26 #[default]
27 Millisecond = 0,
28 Microsecond = 1,
29 Nanosecond = 2,
30 Second = 3,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub enum SeriesKey {
35 DateTime {
36 column: String,
37 precision: TimestampPrecision,
38 },
39 Integer {
40 column: String,
41 },
42}
43
44impl SeriesKey {
45 pub fn column(&self) -> &str {
46 match self {
47 SeriesKey::DateTime {
48 column,
49 ..
50 } => column,
51 SeriesKey::Integer {
52 column,
53 } => column,
54 }
55 }
56
57 pub fn decode(key_kind: u8, precision_raw: u8, column: String) -> Self {
58 match key_kind {
59 1 => SeriesKey::Integer {
60 column,
61 },
62 _ => {
63 let precision = match precision_raw {
64 1 => TimestampPrecision::Microsecond,
65 2 => TimestampPrecision::Nanosecond,
66 3 => TimestampPrecision::Second,
67 _ => TimestampPrecision::Millisecond,
68 };
69 SeriesKey::DateTime {
70 column,
71 precision,
72 }
73 }
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct Series {
80 pub id: SeriesId,
81 pub namespace: NamespaceId,
82 pub name: String,
83 pub columns: Vec<Column>,
84 pub tag: Option<SumTypeId>,
85 pub key: SeriesKey,
86 pub primary_key: Option<PrimaryKey>,
87 pub partition_by: Vec<String>,
88 pub underlying: bool,
89 pub time: TimeSource,
90}
91
92impl Series {
93 pub fn name(&self) -> &str {
94 &self.name
95 }
96
97 pub fn key_column_type(&self) -> Option<ValueType> {
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 pub fn key_to_u64(&self, value: Value) -> Option<u64> {
103 match value {
104 Value::Int1(v) => u64::try_from(v).ok(),
105 Value::Int2(v) => u64::try_from(v).ok(),
106 Value::Int4(v) => u64::try_from(v).ok(),
107 Value::Int8(v) => u64::try_from(v).ok(),
108 Value::Int16(v) => u64::try_from(v).ok(),
109 Value::Uint1(v) => Some(v as u64),
110 Value::Uint2(v) => Some(v as u64),
111 Value::Uint4(v) => Some(v as u64),
112 Value::Uint8(v) => Some(v),
113 Value::Uint16(v) => u64::try_from(v).ok(),
114 Value::DateTime(dt) => {
115 let nanos = dt.to_nanos();
116 match &self.key {
117 SeriesKey::DateTime {
118 precision,
119 ..
120 } => Some(match precision {
121 TimestampPrecision::Second => nanos / 1_000_000_000,
122 TimestampPrecision::Millisecond => nanos / 1_000_000,
123 TimestampPrecision::Microsecond => nanos / 1_000,
124 TimestampPrecision::Nanosecond => nanos,
125 }),
126 _ => Some(nanos),
127 }
128 }
129 _ => None,
130 }
131 }
132
133 pub fn key_from_u64(&self, v: u64) -> Value {
134 let ty = self.key_column_type();
135 match ty.as_ref() {
136 Some(ValueType::Int1) => Value::Int1(v as i8),
137 Some(ValueType::Int2) => Value::Int2(v as i16),
138 Some(ValueType::Int4) => Value::Int4(v as i32),
139 Some(ValueType::Int8) => Value::Int8(v as i64),
140 Some(ValueType::Uint1) => Value::Uint1(v as u8),
141 Some(ValueType::Uint2) => Value::Uint2(v as u16),
142 Some(ValueType::Uint4) => Value::Uint4(v as u32),
143 Some(ValueType::Uint8) => Value::Uint8(v),
144 Some(ValueType::Uint16) => Value::Uint16(v as u128),
145 Some(ValueType::Int16) => Value::Int16(v as i128),
146 Some(ValueType::DateTime) => {
147 let nanos: u64 = match &self.key {
148 SeriesKey::DateTime {
149 precision,
150 ..
151 } => match precision {
152 TimestampPrecision::Second => v * 1_000_000_000,
153 TimestampPrecision::Millisecond => v * 1_000_000,
154 TimestampPrecision::Microsecond => v * 1_000,
155 TimestampPrecision::Nanosecond => v,
156 },
157 _ => v,
158 };
159 Value::DateTime(DateTime::from_nanos(nanos))
160 }
161 _ => Value::Uint8(v),
162 }
163 }
164
165 pub fn key_column_data(&self, keys: Vec<u64>) -> ColumnBuffer {
166 let key_type = self.key_column_type();
167 match &key_type {
168 Some(ty) => {
169 let mut data = ColumnBuffer::with_capacity(ty.clone(), keys.len());
170 for k in keys {
171 data.push_value(self.key_from_u64(k));
172 }
173 data
174 }
175 None => ColumnBuffer::uint8(keys),
176 }
177 }
178
179 pub fn data_columns(&self) -> impl Iterator<Item = &Column> {
180 let key_column = self.key.column().to_string();
181 self.columns.iter().filter(move |c| c.name != key_column)
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct SeriesMetadata {
187 pub row_count: u64,
188 pub oldest_key: u64,
189 pub newest_key: u64,
190 pub sequence_counter: u64,
191}
192
193impl SeriesMetadata {
194 pub fn new() -> Self {
195 Self {
196 row_count: 0,
197 oldest_key: 0,
198 newest_key: 0,
199 sequence_counter: 0,
200 }
201 }
202}
203
204impl Default for SeriesMetadata {
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210const SERIES_METADATA_WIDTH: usize = 32;
211
212pub fn encode_series_metadata(metadata: &SeriesMetadata) -> EncodedPodRow {
213 let mut bytes = Vec::with_capacity(SERIES_METADATA_WIDTH);
214 bytes.extend_from_slice(&metadata.row_count.to_be_bytes());
215 bytes.extend_from_slice(&metadata.oldest_key.to_be_bytes());
216 bytes.extend_from_slice(&metadata.newest_key.to_be_bytes());
217 bytes.extend_from_slice(&metadata.sequence_counter.to_be_bytes());
218 EncodedPodRow::new(&bytes)
219}
220
221pub fn decode_series_metadata(row: &EncodedPodRow) -> Result<SeriesMetadata> {
222 let bytes = row.body();
223 if bytes.len() != SERIES_METADATA_WIDTH {
224 return_internal_error!(
225 "Series metadata is {} bytes wide, expected {}. This indicates a corrupt metadata row.",
226 bytes.len(),
227 SERIES_METADATA_WIDTH
228 )
229 }
230 Ok(SeriesMetadata {
231 row_count: u64::from_be_bytes(bytes[0..8].try_into().unwrap()),
232 oldest_key: u64::from_be_bytes(bytes[8..16].try_into().unwrap()),
233 newest_key: u64::from_be_bytes(bytes[16..24].try_into().unwrap()),
234 sequence_counter: u64::from_be_bytes(bytes[24..32].try_into().unwrap()),
235 })
236}
237
238#[cfg(test)]
239mod series_metadata_tests {
240 use super::*;
241
242 #[test]
243 fn every_field_survives_a_round_trip_at_the_declared_width() {
244 let metadata = SeriesMetadata {
245 row_count: 42,
246 oldest_key: 100,
247 newest_key: 900,
248 sequence_counter: 7,
249 };
250
251 let row = encode_series_metadata(&metadata);
252
253 assert_eq!(row.len(), SERIES_METADATA_WIDTH);
254 assert_eq!(decode_series_metadata(&row).unwrap(), metadata);
255 }
256
257 #[test]
258 fn the_key_bounds_do_not_swap_because_they_select_which_buckets_materialise() {
259 let metadata = SeriesMetadata {
260 row_count: 1,
261 oldest_key: 1,
262 newest_key: u64::MAX,
263 sequence_counter: 0,
264 };
265
266 let decoded = decode_series_metadata(&encode_series_metadata(&metadata)).unwrap();
267
268 assert_eq!(decoded.oldest_key, 1);
269 assert_eq!(decoded.newest_key, u64::MAX);
270 }
271
272 #[test]
273 fn a_row_of_the_wrong_width_is_rejected_rather_than_rewinding_the_sequence_counter() {
274 assert!(decode_series_metadata(&EncodedPodRow::new(&[0u8; 31])).is_err());
275 assert!(decode_series_metadata(&EncodedPodRow::new(&[0u8; 33])).is_err());
276 assert!(decode_series_metadata(&EncodedPodRow::new(&[0u8; 40])).is_err());
277 }
278}