Skip to main content

reifydb_value/value/frame/
frame.rs

1// SPDX-License-Identifier: MIT
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::{Value, datetime::DateTime, row_number::RowNumber},
15};
16
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Frame {
19	pub row_numbers: Vec<RowNumber>,
20	pub created_at: Vec<DateTime>,
21	pub updated_at: Vec<DateTime>,
22	pub columns: Vec<FrameColumn>,
23}
24
25impl Deref for Frame {
26	type Target = [FrameColumn];
27
28	fn deref(&self) -> &Self::Target {
29		&self.columns
30	}
31}
32
33impl Index<usize> for Frame {
34	type Output = FrameColumn;
35
36	fn index(&self, index: usize) -> &Self::Output {
37		self.columns.index(index)
38	}
39}
40
41fn escape_control_chars(s: &str) -> String {
42	s.replace('\n', "\\n").replace('\t', "\\t")
43}
44
45impl Frame {
46	pub fn new(columns: Vec<FrameColumn>) -> Self {
47		Self {
48			row_numbers: Vec::new(),
49			created_at: Vec::new(),
50			updated_at: Vec::new(),
51			columns,
52		}
53	}
54
55	pub fn with_row_numbers(columns: Vec<FrameColumn>, row_numbers: Vec<RowNumber>) -> Self {
56		Self {
57			row_numbers,
58			created_at: Vec::new(),
59			updated_at: Vec::new(),
60			columns,
61		}
62	}
63
64	pub fn to_rows(&self) -> Vec<Vec<(String, Value)>> {
65		let row_count = self.first().map_or(0, |c| c.data.len());
66		(0..row_count)
67			.map(|row_idx| {
68				self.columns.iter().map(|col| (col.name.clone(), col.data.get_value(row_idx))).collect()
69			})
70			.collect()
71	}
72}
73
74impl Display for Frame {
75	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
76		let row_count = self.first().map_or(0, |c| c.data.len());
77		let has_row_numbers = !self.row_numbers.is_empty();
78		let has_created_at = !self.created_at.is_empty();
79		let has_updated_at = !self.updated_at.is_empty();
80
81		let mut col_widths: Vec<usize> = Vec::new();
82
83		if has_row_numbers {
84			let header_width = "#rownum".width();
85			let max_val_width = self.row_numbers.iter().map(|rn| rn.to_string().width()).max().unwrap_or(0);
86			col_widths.push(header_width.max(max_val_width));
87		}
88		if has_created_at {
89			let header_width = "#created_at".width();
90			let max_val_width = self.created_at.iter().map(|ts| ts.to_string().width()).max().unwrap_or(0);
91			col_widths.push(header_width.max(max_val_width));
92		}
93		if has_updated_at {
94			let header_width = "#updated_at".width();
95			let max_val_width = self.updated_at.iter().map(|ts| ts.to_string().width()).max().unwrap_or(0);
96			col_widths.push(header_width.max(max_val_width));
97		}
98
99		for col in &self.columns {
100			let header_width = escape_control_chars(&col.name).width();
101			let mut max_val_width = 0;
102			for i in 0..col.data.len() {
103				max_val_width = max_val_width.max(escape_control_chars(&col.data.as_string(i)).width());
104			}
105			col_widths.push(header_width.max(max_val_width));
106		}
107
108		for w in &mut col_widths {
109			*w += 2;
110		}
111
112		let sep: String = if col_widths.is_empty() {
113			"++".to_string()
114		} else {
115			col_widths.iter().map(|w| format!("+{}", "-".repeat(*w + 2))).collect::<String>() + "+"
116		};
117
118		writeln!(f, "{}", sep)?;
119
120		let mut header_parts = Vec::new();
121		let mut col_idx = 0;
122		if has_row_numbers {
123			let name = "#rownum";
124			let w = col_widths[col_idx];
125			let pad = w - name.width();
126			let l = pad / 2;
127			let r = pad - l;
128			header_parts.push(format!(" {:l$}{}{:r$} ", "", name, ""));
129			col_idx += 1;
130		}
131		if has_created_at {
132			let name = "#created_at";
133			let w = col_widths[col_idx];
134			let pad = w - name.width();
135			let l = pad / 2;
136			let r = pad - l;
137			header_parts.push(format!(" {:l$}{}{:r$} ", "", name, ""));
138			col_idx += 1;
139		}
140		if has_updated_at {
141			let name = "#updated_at";
142			let w = col_widths[col_idx];
143			let pad = w - name.width();
144			let l = pad / 2;
145			let r = pad - l;
146			header_parts.push(format!(" {:l$}{}{:r$} ", "", name, ""));
147			col_idx += 1;
148		}
149		for col in &self.columns {
150			let name = escape_control_chars(&col.name);
151			let w = col_widths[col_idx];
152			let pad = w - name.width();
153			let l = pad / 2;
154			let r = pad - l;
155			header_parts.push(format!(" {:l$}{}{:r$} ", "", name, ""));
156			col_idx += 1;
157		}
158		writeln!(f, "|{}|", header_parts.join("|"))?;
159		writeln!(f, "{}", sep)?;
160
161		for row_idx in 0..row_count {
162			let mut row_parts = Vec::new();
163			let mut col_idx = 0;
164			if has_row_numbers {
165				let w = col_widths[col_idx];
166				let val = self.row_numbers[row_idx].to_string();
167				let pad = w - val.width();
168				let l = pad / 2;
169				let r = pad - l;
170				row_parts.push(format!(" {:l$}{}{:r$} ", "", val, ""));
171				col_idx += 1;
172			}
173			if has_created_at {
174				let w = col_widths[col_idx];
175				let val = self.created_at[row_idx].to_string();
176				let pad = w - val.width();
177				let l = pad / 2;
178				let r = pad - l;
179				row_parts.push(format!(" {:l$}{}{:r$} ", "", val, ""));
180				col_idx += 1;
181			}
182			if has_updated_at {
183				let w = col_widths[col_idx];
184				let val = self.updated_at[row_idx].to_string();
185				let pad = w - val.width();
186				let l = pad / 2;
187				let r = pad - l;
188				row_parts.push(format!(" {:l$}{}{:r$} ", "", val, ""));
189				col_idx += 1;
190			}
191			for col in &self.columns {
192				let w = col_widths[col_idx];
193				let val = escape_control_chars(&col.data.as_string(row_idx));
194				let pad = w - val.width();
195				let l = pad / 2;
196				let r = pad - l;
197				row_parts.push(format!(" {:l$}{}{:r$} ", "", val, ""));
198				col_idx += 1;
199			}
200			writeln!(f, "|{}|", row_parts.join("|"))?;
201		}
202
203		writeln!(f, "{}", sep)
204	}
205}