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			op: None,
94		}
95	}
96}
97
98impl From<FrameColumnData> for ColumnBuffer {
99	fn from(value: FrameColumnData) -> Self {
100		match value {
101			FrameColumnData::Bool(container) => ColumnBuffer::Bool(container),
102			FrameColumnData::Float4(container) => ColumnBuffer::Float4(container),
103			FrameColumnData::Float8(container) => ColumnBuffer::Float8(container),
104			FrameColumnData::Int1(container) => ColumnBuffer::Int1(container),
105			FrameColumnData::Int2(container) => ColumnBuffer::Int2(container),
106			FrameColumnData::Int4(container) => ColumnBuffer::Int4(container),
107			FrameColumnData::Int8(container) => ColumnBuffer::Int8(container),
108			FrameColumnData::Int16(container) => ColumnBuffer::Int16(container),
109			FrameColumnData::Uint1(container) => ColumnBuffer::Uint1(container),
110			FrameColumnData::Uint2(container) => ColumnBuffer::Uint2(container),
111			FrameColumnData::Uint4(container) => ColumnBuffer::Uint4(container),
112			FrameColumnData::Uint8(container) => ColumnBuffer::Uint8(container),
113			FrameColumnData::Uint16(container) => ColumnBuffer::Uint16(container),
114			FrameColumnData::Utf8(container) => ColumnBuffer::Utf8 {
115				container,
116				max_bytes: MaxBytes::MAX,
117			},
118			FrameColumnData::Date(container) => ColumnBuffer::Date(container),
119			FrameColumnData::DateTime(container) => ColumnBuffer::DateTime(container),
120			FrameColumnData::Time(container) => ColumnBuffer::Time(container),
121			FrameColumnData::Duration(container) => ColumnBuffer::Duration(container),
122			FrameColumnData::IdentityId(container) => ColumnBuffer::IdentityId(container),
123			FrameColumnData::Uuid4(container) => ColumnBuffer::Uuid4(container),
124			FrameColumnData::Uuid7(container) => ColumnBuffer::Uuid7(container),
125			FrameColumnData::Blob(container) => ColumnBuffer::Blob {
126				container,
127				max_bytes: MaxBytes::MAX,
128			},
129			FrameColumnData::Int(container) => ColumnBuffer::Int {
130				container,
131				max_bytes: MaxBytes::MAX,
132			},
133			FrameColumnData::Uint(container) => ColumnBuffer::Uint {
134				container,
135				max_bytes: MaxBytes::MAX,
136			},
137			FrameColumnData::Decimal(container) => ColumnBuffer::Decimal {
138				container,
139				precision: Precision::MAX,
140				scale: Scale::new(0),
141			},
142			FrameColumnData::Any(container) => ColumnBuffer::Any(container),
143			FrameColumnData::DictionaryId(container) => ColumnBuffer::DictionaryId(container),
144			FrameColumnData::Option {
145				inner,
146				bitvec,
147			} => ColumnBuffer::Option {
148				inner: Box::new(ColumnBuffer::from(*inner)),
149				bitvec,
150			},
151		}
152	}
153}
154
155impl From<FrameColumn> for ColumnWithName {
156	fn from(value: FrameColumn) -> Self {
157		ColumnWithName::new(Fragment::internal(value.name), value.data.into())
158	}
159}
160
161impl From<Frame> for Columns {
162	fn from(frame: Frame) -> Self {
163		let columns: Vec<ColumnWithName> = frame.columns.into_iter().map(|col| col.into()).collect();
164		let mut names = Vec::with_capacity(columns.len());
165		let mut buffers = Vec::with_capacity(columns.len());
166		for c in columns {
167			names.push(c.name);
168			buffers.push(c.data);
169		}
170		Columns {
171			system: frame.system,
172			columns: buffers,
173			names,
174		}
175	}
176}
177
178#[cfg(test)]
179mod tests {
180	use reifydb_value::value::{datetime::DateTime, row_number::RowNumber, system_columns::SystemColumns};
181
182	use super::*;
183	use crate::value::column::{ColumnWithName, buffer::ColumnBuffer};
184
185	fn columns(time_nanos: [u64; 2]) -> Columns {
186		Columns::with_system(
187			vec![ColumnWithName::new(Fragment::internal("v"), ColumnBuffer::int4(vec![10, 20]))],
188			SystemColumns::new(
189				vec![RowNumber(1), RowNumber(2)],
190				Vec::new(),
191				vec![DateTime::from_nanos(900), DateTime::from_nanos(901)],
192				vec![DateTime::from_nanos(950), DateTime::from_nanos(951)],
193				time_nanos.map(DateTime::from_nanos).to_vec(),
194			),
195		)
196	}
197
198	#[test]
199	fn a_frame_round_trips_the_time_vector() {
200		// Hydration feeds query results straight back into a flow, so a Frame that drops #time
201		// yields Columns whose other system vectors are full-length while time is empty - a shape
202		// the substrate cannot represent, since every row owns a #time.
203		let before = columns([1_700_000_000, 1_700_000_001]);
204
205		let after: Columns = Frame::from(before.clone()).into();
206
207		assert_eq!(
208			after.time().to_vec(),
209			before.time().to_vec(),
210			"#time must survive Columns -> Frame -> Columns"
211		);
212	}
213
214	#[test]
215	fn a_round_tripped_frame_keeps_every_system_vector_the_same_length() {
216		// The failure mode is a length mismatch, not a wrong value, and it only surfaces far
217		// downstream; pin the invariant here instead.
218		let after: Columns = Frame::from(columns([5, 6])).into();
219
220		let rows = after.row_count();
221		assert_eq!(after.row_numbers().len(), rows, "row_numbers");
222		assert_eq!(after.created_at().len(), rows, "created_at");
223		assert_eq!(after.updated_at().len(), rows, "updated_at");
224		assert_eq!(after.time().len(), rows, "time");
225	}
226}