reifydb_client/domain/frame/
display.rs

1// Copyright (c) reifydb.com 2025
2// This file is licensed under the MIT, see license.md file
3
4use std::fmt::{self, Display, Formatter};
5
6use reifydb_type::{Value, util::unicode::UnicodeWidthStr};
7
8use crate::domain::frame::{Frame, FrameColumn};
9
10/// Calculate the display width of a string, handling newlines properly.
11/// For strings with newlines, returns the width of the longest line.
12/// For strings without newlines, returns the unicode display width.
13fn display_width(s: &str) -> usize {
14	if s.contains('\n') {
15		s.lines().map(|line| line.width()).max().unwrap_or(0)
16	} else {
17		s.width()
18	}
19}
20
21/// Escape newlines and tabs in a string for single-line display.
22/// Replaces '\n' with "\\n" and '\t' with "\\t".
23fn escape_control_chars(s: &str) -> String {
24	s.replace('\n', "\\n").replace('\t', "\\t")
25}
26
27/// Create a column display order (no special handling needed since encoded numbers are separate)
28fn get_column_display_order(frame: &Frame) -> Vec<usize> {
29	(0..frame.len()).collect()
30}
31
32/// Extract string value from column at given encoded index, with proper escaping
33fn extract_string_value(col: &FrameColumn, row_number: usize) -> String {
34	let s = col.data.get(row_number).unwrap_or(&Value::Undefined).as_string();
35
36	escape_control_chars(&s)
37}
38
39impl Display for Frame {
40	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
41		let row_count = self.first().map_or(0, |c| c.data.len());
42		let has_row_numbers = !self.row_numbers.is_empty();
43		let col_count = self.len()
44			+ if has_row_numbers {
45				1
46			} else {
47				0
48			};
49
50		// Get the display order for regular columns
51		let column_order = get_column_display_order(self);
52
53		let mut col_widths = vec![0; col_count];
54
55		// If we have encoded numbers, calculate width for encoded number column
56		let row_num_col_idx = if has_row_numbers {
57			// Row number column is always first
58			let row_num_header = "__ROW__NUMBER__";
59			col_widths[0] = display_width(row_num_header);
60
61			// Calculate max width needed for encoded numbers
62			for row_num in &self.row_numbers {
63				let s = row_num.to_string();
64				col_widths[0] = col_widths[0].max(display_width(&s));
65			}
66			1 // Start regular columns at index 1
67		} else {
68			0 // Start regular columns at index 0
69		};
70
71		for (display_idx, &col_idx) in column_order.iter().enumerate() {
72			let col = &self[col_idx];
73			let display_name = escape_control_chars(&col.qualified_name());
74			col_widths[row_num_col_idx + display_idx] = display_width(&display_name);
75		}
76
77		for row_numberx in 0..row_count {
78			for (display_idx, &col_idx) in column_order.iter().enumerate() {
79				let col = &self[col_idx];
80				let s = extract_string_value(col, row_numberx);
81				col_widths[row_num_col_idx + display_idx] =
82					col_widths[row_num_col_idx + display_idx].max(display_width(&s));
83			}
84		}
85
86		// Add padding
87		for w in &mut col_widths {
88			*w += 2;
89		}
90
91		let sep = format!("+{}+", col_widths.iter().map(|w| "-".repeat(*w + 2)).collect::<Vec<_>>().join("+"));
92		writeln!(f, "{}", sep)?;
93
94		let mut header = Vec::new();
95
96		// Add encoded number header if present
97		if has_row_numbers {
98			let w = col_widths[0];
99			let name = "__ROW__NUMBER__";
100			let pad = w - display_width(name);
101			let l = pad / 2;
102			let r = pad - l;
103			header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
104		}
105
106		// Add regular column headers
107		for (display_idx, &col_idx) in column_order.iter().enumerate() {
108			let col = &self[col_idx];
109			let w = col_widths[row_num_col_idx + display_idx];
110			let name = escape_control_chars(&col.qualified_name());
111			let pad = w - display_width(&name);
112			let l = pad / 2;
113			let r = pad - l;
114			header.push(format!(" {:left$}{}{:right$} ", "", name, "", left = l, right = r));
115		}
116
117		writeln!(f, "|{}|", header.join("|"))?;
118
119		writeln!(f, "{}", sep)?;
120
121		for row_numberx in 0..row_count {
122			let mut row = Vec::new();
123
124			// Add encoded number value if present
125			if has_row_numbers {
126				let w = col_widths[0];
127				let s = if row_numberx < self.row_numbers.len() {
128					self.row_numbers[row_numberx].to_string()
129				} else {
130					"Undefined".to_string()
131				};
132				let pad = w - display_width(&s);
133				let l = pad / 2;
134				let r = pad - l;
135				row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
136			}
137
138			// Add regular column values
139			for (display_idx, &col_idx) in column_order.iter().enumerate() {
140				let col = &self[col_idx];
141				let w = col_widths[row_num_col_idx + display_idx];
142				let s = extract_string_value(col, row_numberx);
143				let pad = w - display_width(&s);
144				let l = pad / 2;
145				let r = pad - l;
146				row.push(format!(" {:left$}{}{:right$} ", "", s, "", left = l, right = r));
147			}
148
149			writeln!(f, "|{}|", row.join("|"))?;
150		}
151
152		writeln!(f, "{}", sep)
153	}
154}
155
156#[cfg(test)]
157mod tests {
158	use std::convert::TryFrom;
159
160	use reifydb_type::{
161		Blob, Date, DateTime, Duration, OrderedF32, OrderedF64, RowNumber, Time, Type, Uuid4, Uuid7, Value,
162		parse_uuid4, parse_uuid7,
163	};
164
165	use super::*;
166
167	// Macro to create test columns with optional values (None = undefined)
168	macro_rules! column_with_undefineds {
169		($name:expr, Bool, $data:expr) => {{
170			let result_data: Vec<Value> =
171				$data.into_iter().map(|opt| opt.map_or(Value::Undefined, Value::Boolean)).collect();
172
173			FrameColumn {
174				namespace: None,
175				store: None,
176				name: $name.to_string(),
177				r#type: Type::Boolean,
178				data: result_data,
179			}
180		}};
181		($name:expr, Float4, $data:expr) => {{
182			let result_data: Vec<Value> = $data
183				.into_iter()
184				.map(|opt| {
185					opt.map_or(Value::Undefined, |v| {
186						Value::Float4(OrderedF32::try_from(v).unwrap())
187					})
188				})
189				.collect();
190
191			FrameColumn {
192				namespace: None,
193				store: None,
194				name: $name.to_string(),
195				r#type: Type::Float4,
196				data: result_data,
197			}
198		}};
199		($name:expr, Float8, $data:expr) => {{
200			let result_data: Vec<Value> = $data
201				.into_iter()
202				.map(|opt| {
203					opt.map_or(Value::Undefined, |v| {
204						Value::Float8(OrderedF64::try_from(v).unwrap())
205					})
206				})
207				.collect();
208
209			FrameColumn {
210				namespace: None,
211				store: None,
212				name: $name.to_string(),
213				r#type: Type::Float8,
214				data: result_data,
215			}
216		}};
217		($name:expr, Utf8, $data:expr) => {{
218			let result_data: Vec<Value> = $data
219				.into_iter()
220				.map(|opt| opt.map_or(Value::Undefined, |v| Value::Utf8(v.to_string())))
221				.collect();
222
223			FrameColumn {
224				namespace: None,
225				store: None,
226				name: $name.to_string(),
227				r#type: Type::Utf8,
228				data: result_data,
229			}
230		}};
231		($name:expr, RowNumber, $data:expr) => {{
232			let result_data: Vec<Value> =
233				$data.into_iter().map(|opt| opt.map_or(Value::Undefined, Value::RowNumber)).collect();
234
235			FrameColumn {
236				namespace: None,
237				store: None,
238				name: "__ROW__NUMBER__".to_string(),
239				r#type: Type::RowNumber,
240				data: result_data,
241			}
242		}};
243		($name:expr, $type:ident, $data:expr) => {{
244			let result_data: Vec<Value> =
245				$data.into_iter().map(|opt| opt.map_or(Value::Undefined, Value::$type)).collect();
246
247			FrameColumn {
248				namespace: None,
249				store: None,
250				name: $name.to_string(),
251				r#type: Type::$type,
252				data: result_data,
253			}
254		}};
255	}
256
257	fn undefined_column(name: &str, count: usize) -> FrameColumn {
258		FrameColumn {
259			namespace: None,
260			store: None,
261			name: name.to_string(),
262			r#type: Type::Undefined,
263			data: vec![Value::Undefined; count],
264		}
265	}
266
267	#[test]
268	fn test_bool() {
269		let frame = Frame::new(vec![], vec![column_with_undefineds!("bool", Bool, [Some(true), None])]);
270		let output = format!("{}", frame);
271		let expected = "\
272+-------------+
273|    bool     |
274+-------------+
275|    true     |
276|  Undefined  |
277+-------------+
278";
279		assert_eq!(output, expected);
280	}
281
282	#[test]
283	fn test_float4() {
284		let frame = Frame::new(vec![], vec![column_with_undefineds!("float4", Float4, [Some(1.2_f32), None])]);
285		let output = format!("{}", frame);
286		let expected = "\
287+-------------+
288|   float4    |
289+-------------+
290|     1.2     |
291|  Undefined  |
292+-------------+
293";
294		assert_eq!(output, expected);
295	}
296
297	#[test]
298	#[allow(clippy::approx_constant)]
299	fn test_float8() {
300		let frame = Frame::new(vec![], vec![column_with_undefineds!("float8", Float8, [Some(3.14_f64), None])]);
301		let output = format!("{}", frame);
302		let expected = "\
303+-------------+
304|   float8    |
305+-------------+
306|    3.14     |
307|  Undefined  |
308+-------------+
309";
310		assert_eq!(output, expected);
311	}
312
313	#[test]
314	fn test_int1() {
315		let frame = Frame::new(vec![], vec![column_with_undefineds!("int1", Int1, [Some(1_i8), None])]);
316		let output = format!("{}", frame);
317		let expected = "\
318+-------------+
319|    int1     |
320+-------------+
321|      1      |
322|  Undefined  |
323+-------------+
324";
325		assert_eq!(output, expected);
326	}
327
328	#[test]
329	fn test_int2() {
330		let frame = Frame::new(vec![], vec![column_with_undefineds!("int2", Int2, [Some(100_i16), None])]);
331		let output = format!("{}", frame);
332		let expected = "\
333+-------------+
334|    int2     |
335+-------------+
336|     100     |
337|  Undefined  |
338+-------------+
339";
340		assert_eq!(output, expected);
341	}
342
343	#[test]
344	fn test_int4() {
345		let frame = Frame::new(vec![], vec![column_with_undefineds!("int4", Int4, [Some(1000_i32), None])]);
346		let output = format!("{}", frame);
347		let expected = "\
348+-------------+
349|    int4     |
350+-------------+
351|    1000     |
352|  Undefined  |
353+-------------+
354";
355		assert_eq!(output, expected);
356	}
357
358	#[test]
359	fn test_int8() {
360		let frame = Frame::new(vec![], vec![column_with_undefineds!("int8", Int8, [Some(10000_i64), None])]);
361		let output = format!("{}", frame);
362		let expected = "\
363+-------------+
364|    int8     |
365+-------------+
366|    10000    |
367|  Undefined  |
368+-------------+
369";
370		assert_eq!(output, expected);
371	}
372
373	#[test]
374	fn test_int16() {
375		let frame =
376			Frame::new(vec![], vec![column_with_undefineds!("int16", Int16, [Some(100000_i128), None])]);
377		let output = format!("{}", frame);
378		let expected = "\
379+-------------+
380|    int16    |
381+-------------+
382|   100000    |
383|  Undefined  |
384+-------------+
385";
386		assert_eq!(output, expected);
387	}
388
389	#[test]
390	fn test_uint1() {
391		let frame = Frame::new(vec![], vec![column_with_undefineds!("uint1", Uint1, [Some(1_u8), None])]);
392		let output = format!("{}", frame);
393		let expected = "\
394+-------------+
395|    uint1    |
396+-------------+
397|      1      |
398|  Undefined  |
399+-------------+
400";
401		assert_eq!(output, expected);
402	}
403
404	#[test]
405	fn test_uint2() {
406		let frame = Frame::new(vec![], vec![column_with_undefineds!("uint2", Uint2, [Some(100_u16), None])]);
407		let output = format!("{}", frame);
408		let expected = "\
409+-------------+
410|    uint2    |
411+-------------+
412|     100     |
413|  Undefined  |
414+-------------+
415";
416		assert_eq!(output, expected);
417	}
418
419	#[test]
420	fn test_uint4() {
421		let frame = Frame::new(vec![], vec![column_with_undefineds!("uint4", Uint4, [Some(1000_u32), None])]);
422		let output = format!("{}", frame);
423		let expected = "\
424+-------------+
425|    uint4    |
426+-------------+
427|    1000     |
428|  Undefined  |
429+-------------+
430";
431		assert_eq!(output, expected);
432	}
433
434	#[test]
435	fn test_uint8() {
436		let frame = Frame::new(vec![], vec![column_with_undefineds!("uint8", Uint8, [Some(10000_u64), None])]);
437		let output = format!("{}", frame);
438		let expected = "\
439+-------------+
440|    uint8    |
441+-------------+
442|    10000    |
443|  Undefined  |
444+-------------+
445";
446		assert_eq!(output, expected);
447	}
448
449	#[test]
450	fn test_uint16() {
451		let frame =
452			Frame::new(vec![], vec![column_with_undefineds!("uint16", Uint16, [Some(100000_u128), None])]);
453		let output = format!("{}", frame);
454		let expected = "\
455+-------------+
456|   uint16    |
457+-------------+
458|   100000    |
459|  Undefined  |
460+-------------+
461";
462		assert_eq!(output, expected);
463	}
464
465	#[test]
466	fn test_string() {
467		let frame = Frame::new(vec![], vec![column_with_undefineds!("string", Utf8, [Some("foo"), None])]);
468		let output = format!("{}", frame);
469		let expected = "\
470+-------------+
471|   string    |
472+-------------+
473|     foo     |
474|  Undefined  |
475+-------------+
476";
477		assert_eq!(output, expected);
478	}
479
480	#[test]
481	fn test_undefined() {
482		let frame = Frame::new(vec![], vec![undefined_column("undefined", 2)]);
483		let output = format!("{}", frame);
484		let expected = "\
485+-------------+
486|  undefined  |
487+-------------+
488|  Undefined  |
489|  Undefined  |
490+-------------+
491";
492		assert_eq!(output, expected);
493	}
494
495	#[test]
496	fn test_date() {
497		let frame = Frame::new(
498			vec![],
499			vec![column_with_undefineds!("date", Date, [Some(Date::from_ymd(2025, 1, 15).unwrap()), None])],
500		);
501		let output = format!("{}", frame);
502		let expected = "\
503+--------------+
504|     date     |
505+--------------+
506|  2025-01-15  |
507|  Undefined   |
508+--------------+
509";
510		assert_eq!(output, expected);
511	}
512
513	#[test]
514	fn test_datetime() {
515		let frame = Frame::new(
516			vec![],
517			vec![column_with_undefineds!(
518				"datetime",
519				DateTime,
520				[Some(DateTime::from_timestamp(1642694400).unwrap()), None]
521			)],
522		);
523		let output = format!("{}", frame);
524		let expected = "\
525+----------------------------------+
526|             datetime             |
527+----------------------------------+
528|  2022-01-20T16:00:00.000000000Z  |
529|            Undefined             |
530+----------------------------------+
531";
532		assert_eq!(output, expected);
533	}
534
535	#[test]
536	fn test_time() {
537		let frame = Frame::new(
538			vec![],
539			vec![column_with_undefineds!("time", Time, [Some(Time::from_hms(14, 30, 45).unwrap()), None])],
540		);
541		let output = format!("{}", frame);
542		let expected = "\
543+----------------------+
544|         time         |
545+----------------------+
546|  14:30:45.000000000  |
547|      Undefined       |
548+----------------------+
549";
550		assert_eq!(output, expected);
551	}
552
553	#[test]
554	fn test_interval() {
555		let frame = Frame::new(
556			vec![],
557			vec![column_with_undefineds!("interval", Duration, [Some(Duration::from_days(30)), None])],
558		);
559		let output = format!("{}", frame);
560
561		let expected = "\
562+-------------+
563|  interval   |
564+-------------+
565|    P30D     |
566|  Undefined  |
567+-------------+
568";
569		assert_eq!(output, expected);
570	}
571
572	#[test]
573	fn test_row_number() {
574		let frame = Frame::new(
575			vec![],
576			vec![column_with_undefineds!("__ROW__NUMBER__", RowNumber, [Some(RowNumber(1234)), None])],
577		);
578		let output = format!("{}", frame);
579		let expected = "\
580+-------------------+
581|  __ROW__NUMBER__  |
582+-------------------+
583|       1234        |
584|     Undefined     |
585+-------------------+
586";
587		assert_eq!(output, expected);
588	}
589
590	#[test]
591	fn test_row_number_display() {
592		// Create a frame with regular columns and separate encoded numbers
593		let regular_column = column_with_undefineds!("name", Utf8, [Some("Alice"), Some("Bob")]);
594
595		let age_column = column_with_undefineds!("age", Int4, [Some(25_i32), Some(30_i32)]);
596
597		// Create frame with encoded numbers as separate field
598		let frame = Frame::new(vec![1, 2], vec![regular_column, age_column]);
599		let output = format!("{}", frame);
600
601		// Verify that __ROW__NUMBER__ appears as the first column in the output
602		let lines: Vec<&str> = output.lines().collect();
603		let header_line = lines[1]; // Second line contains the header
604
605		assert!(header_line.starts_with("|  __ROW__NUMBER__"));
606
607		// Check that the first data value in the first encoded is from encoded numbers
608		let first_data_line = lines[3]; // Fourth line contains first data encoded
609		assert!(first_data_line.contains("|         1         |")); // First RowNumber value
610	}
611
612	#[test]
613	fn test_row_number_undefined_display() {
614		// Create a RowNumber column with one undefined value
615		let row_number_column = column_with_undefineds!(
616			"__ROW__NUMBER__",
617			RowNumber,
618			[Some(RowNumber::new(1)), None] /* Second value is
619			                                 * undefined */
620		);
621
622		let frame = Frame::new(vec![], vec![row_number_column]);
623		let output = format!("{}", frame);
624
625		// Verify that undefined RowNumber displays as "Undefined"
626		let lines: Vec<&str> = output.lines().collect();
627		let first_data_line = lines[3]; // First data encoded
628		let second_data_line = lines[4]; // Second data encoded
629
630		assert!(first_data_line.contains("1")); // First RowNumber value
631		assert!(second_data_line.contains("Undefined")); // Second value should be
632		// undefined
633	}
634
635	#[test]
636	fn test_blob() {
637		let frame = Frame::new(
638			vec![],
639			vec![column_with_undefineds!("blob", Blob, [Some(Blob::new(vec![0x01, 0x02, 0x03])), None])],
640		);
641		let output = format!("{}", frame);
642		let expected = "\
643+-------------+
644|    blob     |
645+-------------+
646|  0x010203   |
647|  Undefined  |
648+-------------+
649";
650		assert_eq!(output, expected);
651	}
652
653	#[test]
654	fn test_uuid4() {
655		let frame = Frame::new(
656			vec![],
657			vec![column_with_undefineds!(
658				"uuid4",
659				Uuid4,
660				[Some(Uuid4::from(parse_uuid4("550e8400-e29b-41d4-a716-446655440000").unwrap())), None]
661			)],
662		);
663		let output = format!("{}", frame);
664		let expected = "\
665+----------------------------------------+
666|                 uuid4                  |
667+----------------------------------------+
668|  550e8400-e29b-41d4-a716-446655440000  |
669|               Undefined                |
670+----------------------------------------+
671";
672		assert_eq!(output, expected);
673	}
674
675	#[test]
676	fn test_uuid7() {
677		let frame = Frame::new(
678			vec![],
679			vec![column_with_undefineds!(
680				"uuid7",
681				Uuid7,
682				[Some(Uuid7::from(parse_uuid7("01890a5d-ac96-774b-b9aa-789c0686aaa4").unwrap())), None]
683			)],
684		);
685		let output = format!("{}", frame);
686		let expected = "\
687+----------------------------------------+
688|                 uuid7                  |
689+----------------------------------------+
690|  01890a5d-ac96-774b-b9aa-789c0686aaa4  |
691|               Undefined                |
692+----------------------------------------+
693";
694		assert_eq!(output, expected);
695	}
696}