pretty_sqlite/
pretties.rs1use crate::options::PrettyOptions;
2use crate::{Error, Result};
3use rusqlite::types::ValueRef;
4use rusqlite::{Connection, Params, Rows};
5use std::str::from_utf8;
6use std::sync::OnceLock;
7use tabled::settings::Style;
8
9static DEFAULT_PRETTY_OPTIONS: OnceLock<PrettyOptions> = OnceLock::new();
12
13pub fn print_table(conn: &Connection, table: &str) -> Result<()> {
14 let table_content = pretty_table(conn, table)?;
15 println!("{table_content}");
16
17 Ok(())
18}
19
20pub fn pretty_table(conn: &Connection, table: &str) -> Result<String> {
21 let pretty_options = DEFAULT_PRETTY_OPTIONS.get_or_init(PrettyOptions::default);
22
23 let mut content = format!(" TABLE: {table}\n");
24
25 let select_content = pretty_select_with_options(
26 conn,
27 &format!("SELECT * FROM {table} limit {}", pretty_options.rows_limit),
28 [],
29 pretty_options,
30 )?;
31
32 content.push_str(&select_content);
33
34 Ok(content)
35}
36
37pub fn print_select(conn: &Connection, sql: &str, params: impl Params) -> Result<()> {
38 let select_content = pretty_select(conn, sql, params)?;
39 println!("{select_content}");
40 Ok(())
41}
42
43pub fn pretty_select(conn: &Connection, sql: &str, params: impl Params) -> Result<String> {
44 let pretty_options = DEFAULT_PRETTY_OPTIONS.get_or_init(PrettyOptions::default);
45
46 let mut stmt = conn.prepare(sql)?;
47 let rows = stmt.query(params)?;
48
49 pretty_rows(rows, pretty_options)
50}
51
52pub fn pretty_select_with_options(
53 conn: &Connection,
54 sql: &str,
55 params: impl Params,
56 options: &PrettyOptions,
57) -> Result<String> {
58 let mut stmt = conn.prepare(sql)?;
59 let rows = stmt.query(params)?;
60
61 pretty_rows(rows, options)
62}
63
64pub fn print_rows(rows: Rows<'_>) -> Result<()> {
65 let pretty_options = DEFAULT_PRETTY_OPTIONS.get_or_init(PrettyOptions::default);
66 let rows_content = pretty_rows(rows, pretty_options)?;
67 println!("{rows_content}");
68 Ok(())
69}
70
71fn pretty_rows(mut rows: Rows<'_>, options: &PrettyOptions) -> Result<String> {
72 enum SubType {
73 Time,
74 None,
75 }
76
77 let stmt = rows.as_ref().ok_or(Error::CantPrintRowsHasNoStatement)?;
78 let names: Vec<String> = stmt.column_names().into_iter().map(|s| s.to_string()).collect();
79 let sub_types: Vec<SubType> = names
80 .iter()
81 .map(|n| {
82 if n.ends_with("time") {
83 SubType::Time
84 } else {
85 SubType::None
86 }
87 })
88 .collect();
89
90 let mut table_builder = tabled::builder::Builder::new();
91 table_builder.push_record(names.clone());
92
93 let mut count = 0;
94
95 while let Some(row) = rows.next()? {
96 count += 1;
97 if count > options.rows_limit {
98 break;
99 }
100 let mut cells: Vec<String> = Vec::new();
102 for (i, _k) in names.iter().enumerate() {
103 let v = row.get_ref(i)?;
104 let v = match v {
105 ValueRef::Null => "NULL".to_string(),
106 ValueRef::Integer(num) => match sub_types[i] {
107 SubType::Time => format!("{num}"), SubType::None => format!("{num}"),
109 },
110 ValueRef::Real(num) => format!("{num}"),
111 ValueRef::Text(bytes) => {
112 let txt = format!("\"{}\"", from_utf8(bytes).map_err(|_| Error::SQLiteTextCellIsNotUtf8)?);
113 truncate_string(txt, options)
114 }
115 ValueRef::Blob(blob) => format!("BLOB (length: {})", blob.len()),
116 };
117
118 cells.push(v);
119 }
120
121 table_builder.push_record(cells);
123 }
124
125 let table_content = table_builder.build().with(Style::modern()).to_string();
126
127 Ok(table_content)
128}
129
130fn truncate_string(s: String, options: &PrettyOptions) -> String {
131 let Some(truncate_cell_max) = options.cell_truncate_max else {
132 return s;
133 };
134
135 let max_cell_len = truncate_cell_max as usize;
136
137 let char_indices: Vec<(usize, char)> = s.char_indices().collect();
138 if char_indices.len() > max_cell_len {
139 let first_part_len = max_cell_len / 6; let remaining_len = max_cell_len - first_part_len;
141 let first_part = char_indices[..first_part_len].iter().map(|&(_, c)| c).collect::<String>();
142 let second_part = char_indices[char_indices.len() - remaining_len..]
143 .iter()
144 .map(|&(_, c)| c)
145 .collect::<String>();
146 format!("{first_part}...{second_part}")
147 } else {
148 s.to_string()
149 }
150}