diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 5b335fd34..6307619df 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -127,3 +127,8 @@ path = "cloud.rs" [[example]] name = "execution_profile" path = "execution_profile.rs" + + +[[example]] +name = "displayer" +path = "displayer.rs" \ No newline at end of file diff --git a/examples/displayer.rs b/examples/displayer.rs new file mode 100644 index 000000000..98b52d4e0 --- /dev/null +++ b/examples/displayer.rs @@ -0,0 +1,168 @@ +use anyhow::Result; +use scylla::transport::session::Session; +use scylla::DeserializeRow; +use scylla::QueryRowsResult; +use scylla::SessionBuilder; +use std::env; + +#[tokio::main] +async fn main() -> Result<()> { + let uri = env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9042".to_string()); + + println!("Connecting to {} ...", uri); + + let session: Session = SessionBuilder::new().known_node(uri).build().await?; + + session.query_unpaged("CREATE KEYSPACE IF NOT EXISTS examples_ks WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1}", &[]).await?; + + session + .query_unpaged( + "CREATE TABLE IF NOT EXISTS examples_ks.basic (a int, b int, c text, primary key (a, b))", + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic (a, b, c) VALUES (?, ?, ?)", + (3, 4, "lorem Ipsum jest tekstem stosowanym jako przykładowy wypełniacz w przemyśle poligraficznym. Został po raz pierwszy użyty w XV w. przez nieznanego drukarza do wypełnienia tekstem próbnej książki. Pięć wieków później zaczął być używany przemyśle elektronicznym,"), + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic (a, b, c) VALUES (1, 2, 'abc')", + &[], + ) + .await?; + + let prepared = session + .prepare("INSERT INTO examples_ks.basic (a, b, c) VALUES (?, 7, ?)") + .await?; + session + .execute_unpaged(&prepared, (42_i32, "I'm prepared!")) + .await?; + session + .execute_unpaged(&prepared, (43_i32, "I'm prepared 2!")) + .await?; + session + .execute_unpaged(&prepared, (44_i32, "I'm prepared 3!")) + .await?; + + + // Or as custom structs that derive DeserializeRow + #[allow(unused)] + #[derive(Debug, DeserializeRow)] + struct RowData { + a: i32, + b: Option, + c: String, + } + + let result : QueryRowsResult= session + .query_unpaged("SELECT a, b, c FROM examples_ks.basic", &[]) + .await? + .into_rows_result()?; + + let displayer = result.rows_displayer(); + println!("DISPLAYER:"); + println!("{}", displayer); + + + // example 2 + session + .query_unpaged( + "CREATE TABLE IF NOT EXISTS examples_ks.basic4 (a int, b int, c text, d int, timest timestamp, bytes blob, fl float, db double, time1 time, primary key (a, c))", + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic4 + (a, b, c, d, timest, bytes, fl, db, time1) + VALUES + (1, 10, 'example text', 3, toTimestamp(now()), textAsBlob('sample bytes'), 3.14, 2.718281, '14:30:00');", + &[], + ) + .await?; + + let result2 : QueryRowsResult= session + .query_unpaged("SELECT * FROM examples_ks.basic4", &[]) + .await? + .into_rows_result()?; + + let displayer = result2.rows_displayer(); + println!("DISPLAYER:"); + println!("{}", displayer); + + // example 3 + + session + .query_unpaged( + "CREATE TABLE IF NOT EXISTS examples_ks.basic6 (a int, timud timeuuid, date1 date, ipaddr inet, dur duration, primary key (a))", + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic6 + (a, timud, date1, ipaddr, dur) + VALUES + (1, now(), '2021-01-01', '3.14.15.9', 1h);", + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic6 + (a, timud, date1, ipaddr, dur) + VALUES + (3, NOW(), '2024-01-15', '128.0.0.1', 89h4m48s137ms);", // cqlsh prints this as 89.08003805555556h4.8022833333333335m48.137s137.0ms + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic6 + (a, timud, date1, ipaddr, dur) + VALUES + (4, NOW(), '2024-01-15', '192.168.0.14', 13y2w89h4m48s137ms);", + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic6 (a, timud, date1, ipaddr, dur) + VALUES (2, NOW(), '2024-02-20', '2001:0db8:0:0::1428:57ab', 5d2h);", + &[], + ) + .await?; + + session + .query_unpaged( + "INSERT INTO examples_ks.basic6 (a, timud, date1, ipaddr, dur) + VALUES (5, NOW(), '-250000-02-20', '2001:db8::1428:57ab', 1y1mo1w1d1h1m1s700ms);", + &[], + ) + .await?; + + let result2 : QueryRowsResult= session + .query_unpaged("SELECT * FROM examples_ks.basic6", &[]) + .await? + .into_rows_result()?; + + let mut displayer = result2.rows_displayer(); + println!("DISPLAYER:"); + println!("{}", displayer); + + displayer.set_terminal_width(80); + displayer.use_color(false); + println!("DISPLAYER no color, width = 80:"); + println!("{}", displayer); + Ok(()) +} diff --git a/scylla/Cargo.toml b/scylla/Cargo.toml index d5d9551e2..d60663004 100644 --- a/scylla/Cargo.toml +++ b/scylla/Cargo.toml @@ -77,6 +77,7 @@ base64 = { version = "0.22.1", optional = true } rand_pcg = "0.3.1" socket2 = { version = "0.5.3", features = ["all"] } lazy_static = "1" +tabled = { version = "0.17.0", features = ["std", "ansi"] } [dev-dependencies] num-bigint-03 = { package = "num-bigint", version = "0.3" } @@ -98,4 +99,4 @@ harness = false [lints.rust] unnameable_types = "warn" unreachable_pub = "warn" -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(scylla_cloud_tests)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(scylla_cloud_tests)', 'cfg(cassandra_tests)'] } diff --git a/scylla/src/transport/query_result.rs b/scylla/src/transport/query_result.rs index 114a433f2..ed95742de 100644 --- a/scylla/src/transport/query_result.rs +++ b/scylla/src/transport/query_result.rs @@ -1,11 +1,17 @@ use std::fmt::Debug; +use std::fmt; +use chrono::{Duration, NaiveDate, NaiveTime, TimeZone, Utc}; +use scylla_cql::frame::value::{CqlDate, CqlDuration, CqlTime, CqlTimestamp, CqlTimeuuid}; +use tabled::settings::peaker::Priority; +use tabled::settings::{Alignment, Width}; +use tabled::{builder::Builder, settings::Style, settings::themes::Colorization, settings::Color}; use thiserror::Error; use uuid::Uuid; use scylla_cql::frame::frame_errors::ResultMetadataAndRowsCountParseError; use scylla_cql::frame::response::result::{ - ColumnSpec, ColumnType, DeserializedMetadataAndRawRows, RawMetadataAndRawRows, Row, TableSpec, + ColumnSpec, ColumnType, CqlValue, DeserializedMetadataAndRawRows, RawMetadataAndRawRows, Row, TableSpec }; use scylla_cql::types::deserialize::result::TypedRowIterator; use scylla_cql::types::deserialize::row::DeserializeRow; @@ -433,8 +439,597 @@ impl QueryRowsResult { Err(RowsError::TypeCheckFailed(err)) => Err(SingleRowError::TypeCheckFailed(err)), } } + + pub fn rows_displayer<'a>(&'a self) -> RowsDisplayer<'a> + { + RowsDisplayer::new(self) + } +} + +/// A utility struct for displaying rows received from the database in a [`QueryRowsResult`]. +/// +/// This struct provides methods to configure the display settings and format the rows +/// as a table for easier visualization. It supports various display settings such as +/// terminal width, color usage, and formatting of different CQL value types. +/// +/// # Example +/// +/// ```rust +/// # use scylla::transport::query_result::{QueryResult, QueryRowsResult, RowsDisplayer}; +/// # fn example(query_result: QueryResult) -> Result<(), Box> { +/// let rows_result = query_result.into_rows_result()?; +/// let mut displayer = RowsDisplayer::new(&rows_result); +/// displayer.set_terminal_width(80); +/// displayer.use_color(true); +/// println!("{}", displayer); +/// # Ok(()) +/// # } +/// ``` +/// +/// # Methods +/// +/// - `new(query_result: &'a QueryRowsResult) -> Self` +/// Creates a new `RowsDisplayer` for the given `QueryRowsResult`. +/// +/// - `set_terminal_width(&mut self, terminal_width: usize)` +/// Sets the terminal width for wrapping the table output. +/// +/// - `use_color(&mut self, use_color: bool)` +/// Enables or disables color in the table output. +/// +/// - `fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result` +/// Formats the rows as a table and writes it to the given formatter. +/// +/// - `set_blob_displaying(&mut self, byte_displaying: ByteDisplaying)` +/// Sets the byte display format for blobs. +/// +/// - `set_exponent_displaying_integers(&mut self, exponent_displaying_integers: bool)` +/// Sets the exponent display for integers. +/// +/// - `set_floating_point_precision(&mut self, precision: usize)` +/// Sets the exponent display for floats and doubles. + +pub struct RowsDisplayer<'a> { + query_result: &'a QueryRowsResult, + display_settings: RowsDisplayerSettings, +} + +impl<'a> RowsDisplayer<'a> +{ + /// Creates a new `RowsDisplayer` for the given `QueryRowsResult`. + /// The default settings are: + /// - terminal width: 0, + /// - color usage: true, + /// - byte displaying: `ByteDisplaying::Hex` + pub fn new(query_result: &'a QueryRowsResult) -> Self { + Self { + query_result, + display_settings: RowsDisplayerSettings::new(), + } + } + + /// Sets the terminal width for wrapping the table output. + /// The table will be wrapped to the given width, if possible. + /// If the width is set to 0, the table will not be wrapped. + /// The default value is 0. + pub fn set_terminal_width(&mut self, terminal_width: usize) { + self.display_settings.terminal_width = terminal_width; + } + + /// Enables or disables color in the table output. + pub fn use_color(&mut self, use_color: bool) { + self.display_settings.print_in_color = use_color; + } + + /// Sets the byte display format for blobs. + /// The default value is `ByteDisplaying::Hex`. + /// Possible values are: + /// - `ByteDisplaying::Ascii` - display bytes as ASCII characters, + /// - `ByteDisplaying::Hex` - display bytes as hexadecimal numbers, + /// - `ByteDisplaying::Dec` - display bytes as decimal numbers. + pub fn set_blob_displaying(&mut self, byte_displaying: ByteDisplaying) { + self.display_settings.byte_displaying = byte_displaying; + } + + /// Sets the exponent display for integers. + /// If set to true, integers will be displayed in scientific notation. + /// The default value is false. + pub fn set_exponent_displaying_integers(&mut self, exponent_displaying_integers: bool) { + self.display_settings.exponent_displaying_integers = exponent_displaying_integers; + } + + /// Sets the exponent display for floats and doubles. + pub fn set_floating_point_precision(&mut self, precision: usize) { + self.display_settings.double_precision = precision; + } + + fn get_item_wrapper(&'a self, item: &'a std::option::Option) -> Box> { + match item { + Some(CqlValue::Ascii(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::BigInt(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Blob(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Boolean(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Counter(value)) => { + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Decimal(value)) => { + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Double(value)) => { // TODO set formating for real numbers + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Float(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Int(value)) => { // TODO set formating for integers + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Text(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Timestamp(value)) => { // TOOD set formating for timestamp + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Uuid(value)) => { + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Inet(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::List(value)) => { // TODO set formating for list + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Map(value)) => { // TODO set formating for map + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Set(value)) => { // TODO set formating for set + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::UserDefinedType { keyspace, type_name, fields }) => { // Idk what to do with this + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Tuple(value)) => { // TODO set formating for tuple + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Date(value)) => { // TODO set formating for date + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Duration(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Empty) => { + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::SmallInt(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::TinyInt(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Varint(value)) => { + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + Some(CqlValue::Time(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + Some(CqlValue::Timeuuid(value)) => { + Box::new(WrapperDisplay{value: value, settings: &self.display_settings}) + }, + None => { + Box::new(WrapperDisplay{value: &None, settings: &self.display_settings}) + }, + } + } + + +} + +impl fmt::Display for RowsDisplayer<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let row_iter : TypedRowIterator<'_, '_, Row> = match self.query_result.rows::(){ + Ok(row_iter) => row_iter, + Err(_) => return write!(f, "Error"), + }; + + // put columns names to the table + let column_names : Vec<&str> = self.query_result.column_specs().iter().map(|column_spec| column_spec.name()).collect(); + let mut builder: Builder = Builder::new(); + builder.push_record(column_names); + + // put rows to the table + for row_result in row_iter { + let row_result : Row = match row_result { + Ok(row_result) => row_result, + Err(_) => return write!(f, "Error"), + }; + let columns : Vec> = row_result.columns; + let mut row_values: Vec> = Vec::new(); + for item in &columns { + let wrapper = self.get_item_wrapper(item); + row_values.push(wrapper); + } + builder.push_record(row_values); + } + + let mut table = builder.build(); + + let mut table = table.with(Style::psql()); + table = table.with(Alignment::right()); + if self.display_settings.terminal_width != 0 { + table = table.with( Width::wrap(self.display_settings.terminal_width).priority(Priority::max(true))); + } + + if self.display_settings.print_in_color { + let colum_colors: Vec = self.query_result.column_specs().iter().map(|column_spec| + if *column_spec.typ() == ColumnType::Text{ + Color::FG_YELLOW + } + else if *column_spec.typ() == ColumnType::Blob{ + Color::FG_MAGENTA + } + else { + Color::FG_GREEN + } + ).collect(); + + table = table.with(Colorization::columns(colum_colors)) + .with(Colorization::exact([Color::FG_MAGENTA], tabled::settings::object::Rows::first())); + } + + write!(f, "{}", table) + } } + + +/// Settings for displaying rows in a `RowsDisplayer`. +/// +/// This struct holds various configuration options for formatting and displaying +/// rows received from the database. It includes settings for byte display format, +/// exponent display for floats and integers, precision for doubles, color usage, +/// and terminal width for wrapping the table output. +struct RowsDisplayerSettings { + byte_displaying: ByteDisplaying, // for blobs + exponent_displaying_floats: bool, // for floats + exponent_displaying_integers: bool, // for integers + double_precision: usize, // for doubles + print_in_color: bool, + terminal_width: usize, +} + +impl RowsDisplayerSettings { + fn new() -> Self { // TODO write Default trait + Self { + byte_displaying: ByteDisplaying::Hex, + exponent_displaying_floats: false, + exponent_displaying_integers: false, + double_precision: 5, + print_in_color: true, + terminal_width: 0, + } + } +} + + + +#[derive(PartialEq)] +pub enum ByteDisplaying { + Ascii, + Hex, + Dec, +} + +// wrappers for scylla datatypes implementing Display + +struct WrapperDisplay<'a, T: 'a>{ + value: &'a T, + settings: &'a RowsDisplayerSettings, +} + +// Trait bound to ensure From> for String is implemented +trait StringConvertible<'a>: 'a { + fn to_string(&self) -> String; +} + +// Implement the trait for types that have From> for String +impl<'a, T> StringConvertible<'a> for WrapperDisplay<'a, T> +where + WrapperDisplay<'a, T>: fmt::Display +{ + fn to_string(&self) -> String { + format!("{}", self) + } +} + +// generic impl of From ... for String +impl<'a> From>> for String { + fn from(wrapper: Box>) -> Self { + wrapper.to_string() + } +} + +impl<'a> From<&dyn StringConvertible<'a>> for String { + fn from(wrapper: &dyn StringConvertible<'a>) -> Self { + wrapper.into() + } +} + +impl<'a, T> From> for String +where + T: 'a, + WrapperDisplay<'a, T>: fmt::Display, +{ + fn from(wrapper: WrapperDisplay<'a, T>) -> Self { + format!("{}", wrapper) + } +} + +// Actual implementations of Display for scylla datatypes + +// none WrapperDisplay +impl fmt::Display for WrapperDisplay<'_, std::option::Option> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "None") + } +} + +// tiny int +impl fmt::Display for WrapperDisplay<'_, i8> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.settings.exponent_displaying_floats { + write!(f, "{:e}", self.value) + } else { + write!(f, "{}", self.value) + } + } +} + +// small int +impl fmt::Display for WrapperDisplay<'_, i16> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.settings.exponent_displaying_floats { + write!(f, "{:e}", self.value) + } else { + write!(f, "{}", self.value) + } + } +} + +// int +impl fmt::Display for WrapperDisplay<'_, i32> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.settings.exponent_displaying_floats { + write!(f, "{:e}", self.value) + } else { + write!(f, "{}", self.value) + } + } +} + +// bigint +impl fmt::Display for WrapperDisplay<'_, i64> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.settings.exponent_displaying_floats { + write!(f, "{:e}", self.value) + } else { + write!(f, "{}", self.value) + } + } +} + +// float + +impl fmt::Display for WrapperDisplay<'_, f32> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.settings.exponent_displaying_floats { + write!(f, "{:e}", self.value) + } else { + write!(f, "{}", self.value) + } + } +} + +// double +impl fmt::Display for WrapperDisplay<'_, f64> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.settings.exponent_displaying_floats { + write!(f, "{:e}", self.value) + } else { + write!(f, "{:.digits$}", self.value, digits = self.settings.double_precision) + } + } +} + + +// blob +impl fmt::Display for WrapperDisplay<'_, Vec> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut result = String::new(); + if self.settings.byte_displaying == ByteDisplaying::Hex { + result.push_str("0x"); + } + for byte in self.value { + match self.settings.byte_displaying { + ByteDisplaying::Ascii => { + result.push_str(&format!("{}", *byte as char)); + }, + ByteDisplaying::Hex => { + result.push_str(&format!("{:02x}", byte)); + }, + ByteDisplaying::Dec => { + result.push_str(&format!("{}", byte)); + }, + } + } + write!(f, "{}", result) + } +} + +// string +impl fmt::Display for WrapperDisplay<'_, String> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + +// timestamp +impl fmt::Display for WrapperDisplay<'_, CqlTimestamp> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // egzample of formating timestamp 14:30:00.000000000 + let seconds_from_epoch = self.value.0; + let datetime = Utc.timestamp_millis_opt(seconds_from_epoch) + .single() + .expect("Invalid timestamp"); + + write!(f, "{}.{:06}+0000", + datetime.format("%Y-%m-%d %H:%M:%S"), + datetime.timestamp_subsec_micros()) + } +} + +// time +impl fmt::Display for WrapperDisplay<'_, CqlTime> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // egzample of formating time 14:30:00.000000000 + let nanoseconds = self.value.0; + let total_seconds = nanoseconds / 1_000_000_000; + let hours = total_seconds / 3600; + let minutes = (total_seconds % 3600) / 60; + let seconds = total_seconds % 60; + let nanos = nanoseconds % 1_000_000_000; + + // Create NaiveTime with the calculated components + let time = NaiveTime::from_hms_nano_opt( + hours as u32, + minutes as u32, + seconds as u32, + nanos as u32 + ).expect("Invalid time"); + + // Format the time with 9 digits of nanoseconds + + write!(f, "{}", time.format("%H:%M:%S.%9f")) + } +} + +// timeuuid +impl fmt::Display for WrapperDisplay<'_, CqlTimeuuid> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let uuid = self.value.as_bytes(); + + write!(f, "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], + uuid[6], uuid[7], + uuid[8], uuid[9], + uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]) + } +} + +// duration +impl fmt::Display for WrapperDisplay<'_, CqlDuration> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let months = self.value.months; + let days = self.value.days; + let nanoseconds = self.value.nanoseconds; + + let years = months / 12; + let months = months % 12; + let weeks = days / 7; + let days = days % 7; + let hours = nanoseconds / 3_600_000_000_000; + let minutes = (nanoseconds % 3_600_000_000_000) / 60_000_000_000; + let seconds = (nanoseconds % 60_000_000_000) / 1_000_000_000; + let nanoseconds = nanoseconds % 1_000_000_000; + + let mut result = String::new(); + if years != 0 { + result.push_str(&format!("{}y", years)); + } + + if months != 0 { + result.push_str(&format!("{}mo", months)); + } + + if weeks != 0 { + result.push_str(&format!("{}w", weeks)); + } + + if days != 0 { + result.push_str(&format!("{}d", days)); + } + + if hours != 0 { + result.push_str(&format!("{}h", hours)); + } + + if minutes != 0 { + result.push_str(&format!("{}m", minutes)); + } + + if seconds != 0 { + result.push_str(&format!("{}s", seconds)); + } + + if nanoseconds != 0 { + result.push_str(&format!("{}ns", nanoseconds)); + } + + // Format the time with 9 digits of nanoseconds + write!(f, "{}", result) + } +} + + +// date +impl fmt::Display for WrapperDisplay<'_, CqlDate> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // egzample of formating date 2021-12-31 + let magic_constant = 2055453495; // it is number of days from -5877641-06-23 to -250000-01-01 + // around -250000-01-01 is the limit of naive date + + let days = self.value.0 - magic_constant; + let base_date = NaiveDate::from_ymd_opt(-250000, 1, 1).unwrap(); + + // Add the number of days + let target_date = base_date + Duration::days(days as i64); + + // Format the date + write!(f, "{}", target_date.format("%Y-%m-%d")) + } +} + +// inet +impl fmt::Display for WrapperDisplay<'_, std::net::IpAddr> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let ip = self.value; + match ip { + std::net::IpAddr::V4(ipv4) => { + write!(f, "{}", ipv4) + }, + std::net::IpAddr::V6(ipv6) => { + write!(f, "{}", ipv6) + }, + } + } +} + +// boolean +impl fmt::Display for WrapperDisplay<'_, bool> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.value) + } +} + + /// An error returned by [`QueryResult::into_rows_result`] /// /// The `ResultNotRows` variant contains original [`QueryResult`], @@ -804,6 +1399,24 @@ mod tests { } } } + + // use of QueryRowsResult after use of displayer + { + let rr = sample_raw_rows(2, 1); + let rqr = QueryResult::new(Some(rr), None, Vec::new()); + let qr : QueryRowsResult = rqr.into_rows_result().unwrap(); + let mut displayer = qr.rows_displayer(); + displayer.set_terminal_width(80); + displayer.set_blob_displaying(ByteDisplaying::Hex); + displayer.use_color(true); + let _ = format!("{}", displayer); + let rows = qr.rows::<(&str, bool)>(); + + let mut rows_data = rows.unwrap(); + let row = rows_data.next().unwrap().unwrap(); + + assert_eq!(row, ("MOCK", true)); + } } #[test]