Skip to main content

reifydb_core/value/column/
frame.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_value::{
5	fragment::Fragment,
6	value::{
7		constraint::{bytes::MaxBytes, precision::Precision, scale::Scale},
8		frame::{column::FrameColumn, data::FrameColumnData, frame::Frame},
9	},
10};
11
12use crate::value::column::{ColumnBuffer, ColumnWithName, columns::Columns};
13
14impl From<ColumnBuffer> for FrameColumnData {
15	fn from(value: ColumnBuffer) -> Self {
16		match value {
17			ColumnBuffer::Bool(container) => FrameColumnData::Bool(container),
18			ColumnBuffer::Float4(container) => FrameColumnData::Float4(container),
19			ColumnBuffer::Float8(container) => FrameColumnData::Float8(container),
20			ColumnBuffer::Int1(container) => FrameColumnData::Int1(container),
21			ColumnBuffer::Int2(container) => FrameColumnData::Int2(container),
22			ColumnBuffer::Int4(container) => FrameColumnData::Int4(container),
23			ColumnBuffer::Int8(container) => FrameColumnData::Int8(container),
24			ColumnBuffer::Int16(container) => FrameColumnData::Int16(container),
25			ColumnBuffer::Uint1(container) => FrameColumnData::Uint1(container),
26			ColumnBuffer::Uint2(container) => FrameColumnData::Uint2(container),
27			ColumnBuffer::Uint4(container) => FrameColumnData::Uint4(container),
28			ColumnBuffer::Uint8(container) => FrameColumnData::Uint8(container),
29			ColumnBuffer::Uint16(container) => FrameColumnData::Uint16(container),
30			ColumnBuffer::Utf8 {
31				container,
32				..
33			} => FrameColumnData::Utf8(container),
34			ColumnBuffer::Date(container) => FrameColumnData::Date(container),
35			ColumnBuffer::DateTime(container) => FrameColumnData::DateTime(container),
36			ColumnBuffer::Time(container) => FrameColumnData::Time(container),
37			ColumnBuffer::Duration(container) => FrameColumnData::Duration(container),
38			ColumnBuffer::IdentityId(container) => FrameColumnData::IdentityId(container),
39			ColumnBuffer::Uuid4(container) => FrameColumnData::Uuid4(container),
40			ColumnBuffer::Uuid7(container) => FrameColumnData::Uuid7(container),
41			ColumnBuffer::Blob {
42				container,
43				..
44			} => FrameColumnData::Blob(container),
45			ColumnBuffer::Int {
46				container,
47				..
48			} => FrameColumnData::Int(container),
49			ColumnBuffer::Uint {
50				container,
51				..
52			} => FrameColumnData::Uint(container),
53			ColumnBuffer::Decimal {
54				container,
55				..
56			} => FrameColumnData::Decimal(container),
57			ColumnBuffer::Any(container) => FrameColumnData::Any(container),
58			ColumnBuffer::DictionaryId(container) => FrameColumnData::DictionaryId(container),
59			ColumnBuffer::Option {
60				inner,
61				bitvec,
62			} => FrameColumnData::Option {
63				inner: Box::new(FrameColumnData::from(*inner)),
64				bitvec,
65			},
66		}
67	}
68}
69
70impl From<ColumnWithName> for FrameColumn {
71	fn from(value: ColumnWithName) -> Self {
72		FrameColumn {
73			name: value.name.to_string(),
74			data: value.data.into(),
75		}
76	}
77}
78
79impl From<Columns> for Frame {
80	fn from(columns: Columns) -> Self {
81		let frame_columns: Vec<FrameColumn> = columns
82			.names
83			.iter()
84			.zip(columns.columns.iter())
85			.map(|(name, data)| FrameColumn {
86				name: name.to_string(),
87				data: data.clone().into(),
88			})
89			.collect();
90		Frame {
91			system: columns.system,
92			columns: frame_columns,
93		}
94	}
95}
96
97impl From<FrameColumnData> for ColumnBuffer {
98	fn from(value: FrameColumnData) -> Self {
99		match value {
100			FrameColumnData::Bool(container) => ColumnBuffer::Bool(container),
101			FrameColumnData::Float4(container) => ColumnBuffer::Float4(container),
102			FrameColumnData::Float8(container) => ColumnBuffer::Float8(container),
103			FrameColumnData::Int1(container) => ColumnBuffer::Int1(container),
104			FrameColumnData::Int2(container) => ColumnBuffer::Int2(container),
105			FrameColumnData::Int4(container) => ColumnBuffer::Int4(container),
106			FrameColumnData::Int8(container) => ColumnBuffer::Int8(container),
107			FrameColumnData::Int16(container) => ColumnBuffer::Int16(container),
108			FrameColumnData::Uint1(container) => ColumnBuffer::Uint1(container),
109			FrameColumnData::Uint2(container) => ColumnBuffer::Uint2(container),
110			FrameColumnData::Uint4(container) => ColumnBuffer::Uint4(container),
111			FrameColumnData::Uint8(container) => ColumnBuffer::Uint8(container),
112			FrameColumnData::Uint16(container) => ColumnBuffer::Uint16(container),
113			FrameColumnData::Utf8(container) => ColumnBuffer::Utf8 {
114				container,
115				max_bytes: MaxBytes::MAX,
116			},
117			FrameColumnData::Date(container) => ColumnBuffer::Date(container),
118			FrameColumnData::DateTime(container) => ColumnBuffer::DateTime(container),
119			FrameColumnData::Time(container) => ColumnBuffer::Time(container),
120			FrameColumnData::Duration(container) => ColumnBuffer::Duration(container),
121			FrameColumnData::IdentityId(container) => ColumnBuffer::IdentityId(container),
122			FrameColumnData::Uuid4(container) => ColumnBuffer::Uuid4(container),
123			FrameColumnData::Uuid7(container) => ColumnBuffer::Uuid7(container),
124			FrameColumnData::Blob(container) => ColumnBuffer::Blob {
125				container,
126				max_bytes: MaxBytes::MAX,
127			},
128			FrameColumnData::Int(container) => ColumnBuffer::Int {
129				container,
130				max_bytes: MaxBytes::MAX,
131			},
132			FrameColumnData::Uint(container) => ColumnBuffer::Uint {
133				container,
134				max_bytes: MaxBytes::MAX,
135			},
136			FrameColumnData::Decimal(container) => ColumnBuffer::Decimal {
137				container,
138				precision: Precision::MAX,
139				scale: Scale::new(0),
140			},
141			FrameColumnData::Any(container) => ColumnBuffer::Any(container),
142			FrameColumnData::DictionaryId(container) => ColumnBuffer::DictionaryId(container),
143			FrameColumnData::Option {
144				inner,
145				bitvec,
146			} => ColumnBuffer::Option {
147				inner: Box::new(ColumnBuffer::from(*inner)),
148				bitvec,
149			},
150		}
151	}
152}
153
154impl From<FrameColumn> for ColumnWithName {
155	fn from(value: FrameColumn) -> Self {
156		ColumnWithName::new(Fragment::internal(value.name), value.data.into())
157	}
158}
159
160impl From<Frame> for Columns {
161	fn from(frame: Frame) -> Self {
162		let columns: Vec<ColumnWithName> = frame.columns.into_iter().map(|col| col.into()).collect();
163		let mut names = Vec::with_capacity(columns.len());
164		let mut buffers = Vec::with_capacity(columns.len());
165		for c in columns {
166			names.push(c.name);
167			buffers.push(c.data);
168		}
169		Columns {
170			system: frame.system,
171			columns: buffers,
172			names,
173		}
174	}
175}
176
177#[cfg(test)]
178mod tests {
179	use reifydb_value::value::{datetime::DateTime, row_number::RowNumber, system_columns::SystemColumns};
180
181	use super::*;
182	use crate::value::column::{ColumnWithName, buffer::ColumnBuffer};
183
184	fn columns(time_nanos: [u64; 2]) -> Columns {
185		Columns::with_system(
186			vec![ColumnWithName::new(Fragment::internal("v"), ColumnBuffer::int4(vec![10, 20]))],
187			SystemColumns::new(
188				vec![RowNumber(1), RowNumber(2)],
189				Vec::new(),
190				vec![DateTime::from_nanos(900), DateTime::from_nanos(901)],
191				vec![DateTime::from_nanos(950), DateTime::from_nanos(951)],
192				time_nanos.map(DateTime::from_nanos).to_vec(),
193			),
194		)
195	}
196
197	#[test]
198	fn a_frame_round_trips_the_time_vector() {
199		// Hydration feeds query results straight back into a flow, so a Frame that drops #time
200		// yields Columns whose other system vectors are full-length while time is empty - a shape
201		// the substrate cannot represent, since every row owns a #time.
202		let before = columns([1_700_000_000, 1_700_000_001]);
203
204		let after: Columns = Frame::from(before.clone()).into();
205
206		assert_eq!(
207			after.time().to_vec(),
208			before.time().to_vec(),
209			"#time must survive Columns -> Frame -> Columns"
210		);
211	}
212
213	#[test]
214	fn a_round_tripped_frame_keeps_every_system_vector_the_same_length() {
215		// The failure mode is a length mismatch, not a wrong value, and it only surfaces far
216		// downstream; pin the invariant here instead.
217		let after: Columns = Frame::from(columns([5, 6])).into();
218
219		let rows = after.row_count();
220		assert_eq!(after.row_numbers().len(), rows, "row_numbers");
221		assert_eq!(after.created_at().len(), rows, "created_at");
222		assert_eq!(after.updated_at().len(), rows, "updated_at");
223		assert_eq!(after.time().len(), rows, "time");
224	}
225}