Skip to main content

reifydb_value/value/frame/
frame.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	fmt::{self, Display, Formatter},
6	ops::{Deref, Index},
7};
8
9use serde::{Deserialize, Serialize};
10
11use super::column::FrameColumn;
12use crate::{
13	util::unicode::UnicodeWidthStr,
14	value::{
15		Value,
16		datetime::DateTime,
17		diff_type::DiffType,
18		row_number::RowNumber,
19		system_columns::{SystemColumn, SystemColumns},
20	},
21};
22
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24pub struct Frame {
25	pub system: SystemColumns,
26	pub columns: Vec<FrameColumn>,
27	pub op: Option<DiffType>,
28}
29
30impl Frame {
31	#[inline]
32	pub fn row_numbers(&self) -> &[RowNumber] {
33		self.system.row_numbers()
34	}
35
36	#[inline]
37	pub fn created_at(&self) -> &[DateTime] {
38		self.system.created_at()
39	}
40
41	#[inline]
42	pub fn updated_at(&self) -> &[DateTime] {
43		self.system.updated_at()
44	}
45
46	#[inline]
47	pub fn time(&self) -> &[DateTime] {
48		self.system.time()
49	}
50}
51
52impl Deref for Frame {
53	type Target = [FrameColumn];
54
55	fn deref(&self) -> &Self::Target {
56		&self.columns
57	}
58}
59
60impl Index<usize> for Frame {
61	type Output = FrameColumn;
62
63	fn index(&self, index: usize) -> &Self::Output {
64		self.columns.index(index)
65	}
66}
67
68fn escape_control_chars(s: &str) -> String {
69	s.replace('\n', "\\n").replace('\t', "\\t")
70}
71
72fn present_system_columns(frame: &Frame) -> Vec<(&'static str, Vec<String>)> {
73	let candidates = [
74		(
75			SystemColumn::RowNumbers.name(),
76			frame.row_numbers().iter().map(|v| v.to_string()).collect::<Vec<_>>(),
77		),
78		(SystemColumn::CreatedAt.name(), frame.created_at().iter().map(|v| v.to_string()).collect()),
79		(SystemColumn::UpdatedAt.name(), frame.updated_at().iter().map(|v| v.to_string()).collect()),
80	];
81	candidates.into_iter().filter(|(_, cells)| !cells.is_empty()).collect()
82}
83
84fn centered(width: usize, content: &str) -> String {
85	let pad = width - content.width();
86	let l = pad / 2;
87	let r = pad - l;
88	format!(" {:l$}{}{:r$} ", "", content, "")
89}
90
91impl Frame {
92	pub fn new(columns: Vec<FrameColumn>) -> Self {
93		Self {
94			system: SystemColumns::empty(),
95			columns,
96			op: None,
97		}
98	}
99
100	pub fn with_op(mut self, op: DiffType) -> Self {
101		self.op = Some(op);
102		self
103	}
104
105	pub fn with_row_numbers(columns: Vec<FrameColumn>, row_numbers: Vec<RowNumber>) -> Self {
106		Self {
107			system: SystemColumns::new(row_numbers, Vec::new(), Vec::new(), Vec::new(), Vec::new()),
108			columns,
109			op: None,
110		}
111	}
112
113	pub fn to_rows(&self) -> Vec<Vec<(String, Value)>> {
114		let row_count = self.first().map_or(0, |c| c.data.len());
115		(0..row_count)
116			.map(|row_idx| {
117				self.columns.iter().map(|col| (col.name.clone(), col.data.get_value(row_idx))).collect()
118			})
119			.collect()
120	}
121}
122
123impl Display for Frame {
124	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
125		let row_count = self.first().map_or(0, |c| c.data.len());
126		let system = present_system_columns(self);
127
128		let mut col_widths: Vec<usize> = Vec::new();
129
130		for (header, cells) in &system {
131			let max_val_width = cells.iter().map(|c| c.width()).max().unwrap_or(0);
132			col_widths.push(header.width().max(max_val_width));
133		}
134
135		for col in &self.columns {
136			let header_width = escape_control_chars(&col.name).width();
137			let mut max_val_width = 0;
138			for i in 0..col.data.len() {
139				max_val_width = max_val_width.max(escape_control_chars(&col.data.as_string(i)).width());
140			}
141			col_widths.push(header_width.max(max_val_width));
142		}
143
144		for w in &mut col_widths {
145			*w += 2;
146		}
147
148		let sep: String = if col_widths.is_empty() {
149			"++".to_string()
150		} else {
151			col_widths.iter().map(|w| format!("+{}", "-".repeat(*w + 2))).collect::<String>() + "+"
152		};
153
154		writeln!(f, "{}", sep)?;
155
156		let mut header_parts = Vec::new();
157		for (col_idx, (header, _)) in system.iter().enumerate() {
158			header_parts.push(centered(col_widths[col_idx], header));
159		}
160		for (offset, col) in self.columns.iter().enumerate() {
161			let name = escape_control_chars(&col.name);
162			header_parts.push(centered(col_widths[system.len() + offset], &name));
163		}
164		writeln!(f, "|{}|", header_parts.join("|"))?;
165		writeln!(f, "{}", sep)?;
166
167		for row_idx in 0..row_count {
168			let mut row_parts = Vec::new();
169			for (col_idx, (_, cells)) in system.iter().enumerate() {
170				row_parts.push(centered(col_widths[col_idx], &cells[row_idx]));
171			}
172			for (offset, col) in self.columns.iter().enumerate() {
173				let val = escape_control_chars(&col.data.as_string(row_idx));
174				row_parts.push(centered(col_widths[system.len() + offset], &val));
175			}
176			writeln!(f, "|{}|", row_parts.join("|"))?;
177		}
178
179		writeln!(f, "{}", sep)
180	}
181}