diff --git a/Cargo.toml b/Cargo.toml index d8772df..5d7baa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" description = "Dig into ClickHouse with TUI interface" license = "MIT" version = "25.6.2" -edition = "2021" +edition = "2024" [lib] name = "chdig" @@ -93,6 +93,7 @@ lto = false [lints.clippy] needless_return = "allow" type_complexity = "allow" +uninlined_format_args = "allow" [lints.rust] elided_lifetimes_in_paths = "deny" diff --git a/src/bin.rs b/src/bin.rs index 86c14fb..4bb8c0b 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use backtrace::Backtrace; use flexi_logger::{LogSpecification, Logger}; use std::ffi::OsString; @@ -6,7 +6,7 @@ use std::panic::{self, PanicHookInfo}; use std::sync::Arc; use crate::{ - interpreter::{options, ClickHouse, Context, ContextArc}, + interpreter::{ClickHouse, Context, ContextArc, options}, view::Navigation, }; @@ -92,7 +92,7 @@ fn collect_args(argc: c_int, argv: *const *const c_char) -> Vec { } use std::os::raw::{c_char, c_int}; -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn chdig_main(argc: c_int, argv: *const *const c_char) -> c_int { tokio::runtime::Builder::new_current_thread() .enable_all() diff --git a/src/interpreter/background_runner.rs b/src/interpreter/background_runner.rs index 46912c5..c567c12 100644 --- a/src/interpreter/background_runner.rs +++ b/src/interpreter/background_runner.rs @@ -1,4 +1,4 @@ -use std::sync::{atomic, Arc, Condvar, Mutex}; +use std::sync::{Arc, Condvar, Mutex, atomic}; use std::thread; use std::time::Duration; @@ -52,13 +52,15 @@ impl BackgroundRunner { let cv = self.cv.clone(); let exit = self.exit.clone(); let force = self.force.clone(); - self.thread = Some(std::thread::spawn(move || loop { - let was_force = force.swap(false, atomic::Ordering::SeqCst); - callback(was_force); + self.thread = Some(std::thread::spawn(move || { + loop { + let was_force = force.swap(false, atomic::Ordering::SeqCst); + callback(was_force); - let _ = cv.1.wait_timeout(cv.0.lock().unwrap(), interval).unwrap(); - if *exit.lock().unwrap() { - break; + let _ = cv.1.wait_timeout(cv.0.lock().unwrap(), interval).unwrap(); + if *exit.lock().unwrap() { + break; + } } })); // Explicitly trigger at least one update with force diff --git a/src/interpreter/clickhouse.rs b/src/interpreter/clickhouse.rs index 1e38784..9d35ea5 100644 --- a/src/interpreter/clickhouse.rs +++ b/src/interpreter/clickhouse.rs @@ -1,9 +1,9 @@ -use crate::interpreter::{options::ClickHouseOptions, ClickHouseAvailableQuirks, ClickHouseQuirks}; +use crate::interpreter::{ClickHouseAvailableQuirks, ClickHouseQuirks, options::ClickHouseOptions}; use anyhow::{Error, Result}; use chrono::{DateTime, Local}; use clickhouse_rs::{ - types::{Complex, FromSql}, Block, Options, Pool, + types::{Complex, FromSql}, }; use futures_util::StreamExt; use std::collections::HashMap; diff --git a/src/interpreter/context.rs b/src/interpreter/context.rs index 6810836..7937959 100644 --- a/src/interpreter/context.rs +++ b/src/interpreter/context.rs @@ -1,9 +1,9 @@ use crate::actions::ActionDescription; -use crate::interpreter::{options::ChDigOptions, ClickHouse, Worker}; +use crate::interpreter::{ClickHouse, Worker, options::ChDigOptions}; use anyhow::Result; use chrono::Duration; -use cursive::{event::Event, event::EventResult, views::Dialog, views::OnEventView, Cursive, View}; -use std::sync::{atomic, Arc, Condvar, Mutex}; +use cursive::{Cursive, View, event::Event, event::EventResult, views::Dialog, views::OnEventView}; +use std::sync::{Arc, Condvar, Mutex, atomic}; pub type ContextArc = Arc>; diff --git a/src/interpreter/flamegraph.rs b/src/interpreter/flamegraph.rs index 3a3f0b5..25cc318 100644 --- a/src/interpreter/flamegraph.rs +++ b/src/interpreter/flamegraph.rs @@ -7,13 +7,13 @@ use flamelens::flame::FlameGraph; use flamelens::handler::handle_key_events; use flamelens::ui; use futures::channel::mpsc; -use ratatui::backend::CrosstermBackend; use ratatui::Terminal; +use ratatui::backend::CrosstermBackend; use std::io; -use tokio::time::{sleep, Duration}; +use tokio::time::{Duration, sleep}; use urlencoding::encode; -use warp::http::header::{HeaderMap, HeaderValue}; use warp::Filter; +use warp::http::header::{HeaderMap, HeaderValue}; pub fn show(block: Columns) -> AppResult<()> { let data = block @@ -45,10 +45,10 @@ pub fn show(block: Columns) -> AppResult<()> { while app.running { terminal.draw(|frame| { ui::render(&mut app, frame); - if let Some(input_buffer) = &app.input_buffer { - if let Some(cursor) = input_buffer.cursor { - frame.set_cursor_position((cursor.0, cursor.1)); - } + if let Some(input_buffer) = &app.input_buffer + && let Some(cursor) = input_buffer.cursor + { + frame.set_cursor_position((cursor.0, cursor.1)); } })?; diff --git a/src/interpreter/options.rs b/src/interpreter/options.rs index 56ece82..065e650 100644 --- a/src/interpreter/options.rs +++ b/src/interpreter/options.rs @@ -1,8 +1,8 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use chrono::{DateTime, Duration, Local, NaiveDate, NaiveDateTime}; -use clap::{builder::ArgPredicate, ArgAction, Args, CommandFactory, Parser, Subcommand}; -use clap_complete::{generate, Shell}; -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use clap::{ArgAction, Args, CommandFactory, Parser, Subcommand, builder::ArgPredicate}; +use clap_complete::{Shell, generate}; +use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode}; use quick_xml::de::Deserializer as XmlDeserializer; use serde::Deserialize; use serde_yaml::Deserializer as YamlDeserializer; @@ -198,7 +198,7 @@ pub fn parse_datetime_or_date(value: &str) -> Result, String> { .and_hms_opt(0, 0, 0) .unwrap() .and_local_timezone(Local) - .unwrap()) + .unwrap()); } Err(err) => errors.push(err), } @@ -354,13 +354,13 @@ fn is_local_address(host: &str) -> bool { } fn set_password_from_opt(url: &mut url::Url, password: Option, force: bool) -> Result<()> { - if let Some(password) = password { - if url.password().is_none() || force { - url.set_password(Some( - &utf8_percent_encode(&password, NON_ALPHANUMERIC).to_string(), - )) - .map_err(|_| anyhow!("password is invalid"))?; - } + if let Some(password) = password + && (url.password().is_none() || force) + { + url.set_password(Some( + &utf8_percent_encode(&password, NON_ALPHANUMERIC).to_string(), + )) + .map_err(|_| anyhow!("password is invalid"))?; } Ok(()) } @@ -415,22 +415,22 @@ fn clickhouse_url_defaults( // config // if let Some(config) = config { - if url.username().is_empty() { - if let Some(user) = config.user { - url.set_username(user.as_str()) - .map_err(|_| anyhow!("username is invalid"))?; - } + if url.username().is_empty() + && let Some(user) = config.user + { + url.set_username(user.as_str()) + .map_err(|_| anyhow!("username is invalid"))?; } set_password_from_opt(&mut url, config.password, false)?; - if secure.is_none() { - if let Some(conf_secure) = config.secure { - secure = Some(conf_secure); - } + if secure.is_none() + && let Some(conf_secure) = config.secure + { + secure = Some(conf_secure); } let ssl_client = config.open_ssl.and_then(|ssl| ssl.client); - if skip_verify.is_none() { - if let Some(conf_skip_verify) = config + if skip_verify.is_none() + && let Some(conf_skip_verify) = config .skip_verify .or(config.accept_invalid_certificate) .or_else(|| { @@ -438,28 +438,25 @@ fn clickhouse_url_defaults( .as_ref() .map(|client| client.verification_mode == Some("none".to_string())) }) - { - skip_verify = Some(conf_skip_verify); - } + { + skip_verify = Some(conf_skip_verify); } - if ca_certificate.is_none() { - if let Some(conf_ca_certificate) = ssl_client.as_ref().map(|v| v.ca_config.clone()) { - ca_certificate = conf_ca_certificate.clone(); - } + if ca_certificate.is_none() + && let Some(conf_ca_certificate) = ssl_client.as_ref().map(|v| v.ca_config.clone()) + { + ca_certificate = conf_ca_certificate.clone(); } - if client_certificate.is_none() { - if let Some(conf_client_certificate) = + if client_certificate.is_none() + && let Some(conf_client_certificate) = ssl_client.as_ref().map(|v| v.certificate_file.clone()) - { - client_certificate = conf_client_certificate.clone(); - } + { + client_certificate = conf_client_certificate.clone(); } - if client_private_key.is_none() { - if let Some(conf_client_private_key) = + if client_private_key.is_none() + && let Some(conf_client_private_key) = ssl_client.as_ref().map(|v| v.private_key_file.clone()) - { - client_private_key = conf_client_private_key.clone(); - } + { + client_private_key = conf_client_private_key.clone(); } // @@ -476,48 +473,46 @@ fn clickhouse_url_defaults( } connection_found = true; - if !has_host { - if let Some(hostname) = &c.hostname { - url.set_host(Some(hostname.as_str()))?; - } + if !has_host && let Some(hostname) = &c.hostname { + url.set_host(Some(hostname.as_str()))?; } - if url.port().is_none() { - if let Some(port) = c.port { - url.set_port(Some(port)) - .map_err(|_| anyhow!("Cannot set port"))?; - } + if url.port().is_none() + && let Some(port) = c.port + { + url.set_port(Some(port)) + .map_err(|_| anyhow!("Cannot set port"))?; } - if url.username().is_empty() { - if let Some(user) = &c.user { - url.set_username(user.as_str()) - .map_err(|_| anyhow!("username is invalid"))?; - } + if url.username().is_empty() + && let Some(user) = &c.user + { + url.set_username(user.as_str()) + .map_err(|_| anyhow!("username is invalid"))?; } set_password_from_opt(&mut url, c.password.clone(), false)?; - if secure.is_none() { - if let Some(con_secure) = c.secure { - secure = Some(con_secure); - } + if secure.is_none() + && let Some(con_secure) = c.secure + { + secure = Some(con_secure); } - if skip_verify.is_none() { - if let Some(con_skip_verify) = c.skip_verify { - skip_verify = Some(con_skip_verify); - } + if skip_verify.is_none() + && let Some(con_skip_verify) = c.skip_verify + { + skip_verify = Some(con_skip_verify); } - if ca_certificate.is_none() { - if let Some(con_ca_certificate) = &c.ca_certificate { - ca_certificate = Some(con_ca_certificate.clone()); - } + if ca_certificate.is_none() + && let Some(con_ca_certificate) = &c.ca_certificate + { + ca_certificate = Some(con_ca_certificate.clone()); } - if client_certificate.is_none() { - if let Some(con_client_certificate) = &c.client_certificate { - client_certificate = Some(con_client_certificate.clone()); - } + if client_certificate.is_none() + && let Some(con_client_certificate) = &c.client_certificate + { + client_certificate = Some(con_client_certificate.clone()); } - if client_private_key.is_none() { - if let Some(con_client_private_key) = &c.client_private_key { - client_private_key = Some(con_client_private_key.clone()); - } + if client_private_key.is_none() + && let Some(con_client_private_key) = &c.client_private_key + { + client_private_key = Some(con_client_private_key.clone()); } } diff --git a/src/interpreter/worker.rs b/src/interpreter/worker.rs index d23c072..d173a50 100644 --- a/src/interpreter/worker.rs +++ b/src/interpreter/worker.rs @@ -1,11 +1,11 @@ use crate::{ common::Stopwatch, interpreter::clickhouse::{Columns, TraceType}, - interpreter::{flamegraph, ContextArc}, + interpreter::{ContextArc, flamegraph}, utils::{highlight_sql, open_graph_in_browser}, view::{self, Navigation}, }; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use chrono::{DateTime, Local}; // FIXME: "leaky abstractions" use clickhouse_rs::errors::Error as ClickHouseError; @@ -207,13 +207,16 @@ async fn start_tokio(context: ContextArc, receiver: ReceiverArc) { .cluster .as_ref() .is_some_and(|v| !v.is_empty()); - if has_cluster { - if let Some(ClickHouseError::Server(server_error)) = &err.downcast_ref::() { - if server_error.code == CLICKHOUSE_ERROR_CODE_ALL_CONNECTION_TRIES_FAILED { - siv.add_layer(views::Dialog::info(format!("{}\n(consider adding skip_unavailable_shards=1 to the connection URL)", err))); - return; - } - } + if has_cluster + && let Some(ClickHouseError::Server(server_error)) = + &err.downcast_ref::() + && server_error.code == CLICKHOUSE_ERROR_CODE_ALL_CONNECTION_TRIES_FAILED + { + siv.add_layer(views::Dialog::info(format!( + "{}\n(consider adding skip_unavailable_shards=1 to the connection URL)", + err + ))); + return; } siv.add_layer(views::Dialog::info(err.to_string())); diff --git a/src/utils.rs b/src/utils.rs index 74a0820..e8ac72b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use cursive::utils::markup::StyledString; use std::collections::HashMap; use std::env; use std::fs; -use std::io::{stdout, Write}; +use std::io::{Write, stdout}; use std::process::{Command, Stdio}; use syntect::{highlighting::ThemeSet, parsing::SyntaxSet}; use tempfile::Builder; diff --git a/src/view/log_view.rs b/src/view/log_view.rs index aee6388..c74d3f5 100644 --- a/src/view/log_view.rs +++ b/src/view/log_view.rs @@ -1,15 +1,16 @@ use anyhow::{Error, Result}; use chrono::{DateTime, Local}; use cursive::{ + Cursive, Printer, Vec2, event::{Callback, Event, EventResult, Key}, theme::{BaseColor, Color, ColorStyle}, utils::{ lines::spans::{LinesIterator, Row}, markup::StyledString, }, - view::{scroll, Nameable, Resizable, ScrollStrategy, View, ViewWrapper}, + view::{Nameable, Resizable, ScrollStrategy, View, ViewWrapper, scroll}, views::{Dialog, EditView, NamedView, OnEventView}, - wrap_impl, Cursive, Printer, Vec2, + wrap_impl, }; use unicode_width::UnicodeWidthStr; @@ -156,10 +157,10 @@ impl LogViewBase { fn update_search(&mut self) -> Option { // In case of resize we can have less rows then before, // so reset the matched_row for this scenario to avoid out-of-bound access. - if let Some(rows) = self.rows.as_ref() { - if rows.len() < self.matched_row.unwrap_or_default() { - self.matched_row = None; - } + if let Some(rows) = self.rows.as_ref() + && rows.len() < self.matched_row.unwrap_or_default() + { + self.matched_row = None; } if self.search_direction_forward { return self.update_search_forward(); diff --git a/src/view/navigation.rs b/src/view/navigation.rs index 04b3cc5..ec28dad 100644 --- a/src/view/navigation.rs +++ b/src/view/navigation.rs @@ -2,14 +2,15 @@ use crate::utils::fuzzy_actions; use crate::{ interpreter::{ - clickhouse::TraceType, - options::{parse_datetime_or_date, ChDigViews}, ContextArc, WorkerEvent, + clickhouse::TraceType, + options::{ChDigViews, parse_datetime_or_date}, }, view::{self, TextLogView}, }; use anyhow::Result; use cursive::{ + Cursive, event::{Event, EventResult, Key}, theme::{BaseColor, Color, ColorStyle, Effect, PaletteColor, Style, Theme}, utils::{markup::StyledString, span::SpannedString}, @@ -19,7 +20,7 @@ use cursive::{ Dialog, DummyView, EditView, FixedLayout, Layer, LinearLayout, OnEventView, OnLayoutView, SelectView, TextContent, TextView, }, - Cursive, {Rect, Vec2}, + {Rect, Vec2}, }; use cursive_flexi_logger_view::toggle_flexi_logger_debug_console; use std::collections::HashMap; diff --git a/src/view/processes_view.rs b/src/view/processes_view.rs index 61a7425..99946ad 100644 --- a/src/view/processes_view.rs +++ b/src/view/processes_view.rs @@ -8,18 +8,18 @@ use std::sync::{Arc, Mutex}; use cursive::traits::{Nameable, Resizable}; use cursive::{ + Cursive, event::{Callback, Event, EventResult}, inner_getters, view::ViewWrapper, views::{self, Dialog, EditView, OnEventView}, - Cursive, }; use size::{Base, SizeFormatter, Style}; use crate::{ interpreter::{ - clickhouse::Columns, clickhouse::TraceType, options::ViewOptions, BackgroundRunner, - ContextArc, QueryProcess, WorkerEvent, + BackgroundRunner, ContextArc, QueryProcess, WorkerEvent, clickhouse::Columns, + clickhouse::TraceType, options::ViewOptions, }, utils::{edit_query, get_query}, view::{ExtTableView, ProcessView, QueryResultView, TableViewItem, TextLogView}, diff --git a/src/view/query_result_view.rs b/src/view/query_result_view.rs index da159dc..891d061 100644 --- a/src/view/query_result_view.rs +++ b/src/view/query_result_view.rs @@ -1,17 +1,17 @@ use std::cmp::Ordering; use std::sync::Arc; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use size::{Base, SizeFormatter, Style}; -use crate::interpreter::{clickhouse::Columns, BackgroundRunner, ContextArc, WorkerEvent}; +use crate::interpreter::{BackgroundRunner, ContextArc, WorkerEvent, clickhouse::Columns}; use crate::view::{ExtTableView, TableViewItem}; use crate::wrap_impl_no_move; use chrono::{DateTime, Local}; use chrono_tz::Tz; use clickhouse_rs::types::SqlType; -use cursive::view::ViewWrapper; use cursive::Cursive; +use cursive::view::ViewWrapper; #[derive(Clone, Debug, PartialEq, PartialOrd)] pub enum Field { diff --git a/src/view/summary_view.rs b/src/view/summary_view.rs index 8c45732..090d300 100644 --- a/src/view/summary_view.rs +++ b/src/view/summary_view.rs @@ -1,10 +1,11 @@ use chrono::{DateTime, Local}; use cursive::{ + Printer, Vec2, event::{AnyCb, Event, EventResult}, theme::BaseColor, utils::markup::StyledString, view::{Finder, Nameable, Resizable, Selector, View}, - views, Printer, Vec2, + views, }; use humantime::format_duration; use size::{Base, SizeFormatter, Style}; @@ -12,7 +13,7 @@ use std::rc::Rc; use std::time::Duration; use crate::interpreter::{ - clickhouse::ClickHouseServerSummary, BackgroundRunner, ContextArc, WorkerEvent, + BackgroundRunner, ContextArc, WorkerEvent, clickhouse::ClickHouseServerSummary, }; pub struct SummaryView { diff --git a/src/view/text_log_view.rs b/src/view/text_log_view.rs index 5b160f8..8f36129 100644 --- a/src/view/text_log_view.rs +++ b/src/view/text_log_view.rs @@ -5,7 +5,7 @@ use chrono::{DateTime, Duration, Local}; use chrono_tz::Tz; use cursive::view::ViewWrapper; -use crate::interpreter::{clickhouse::Columns, BackgroundRunner, ContextArc, WorkerEvent}; +use crate::interpreter::{BackgroundRunner, ContextArc, WorkerEvent, clickhouse::Columns}; use crate::view::{LogEntry, LogView}; use crate::wrap_impl_no_move; @@ -42,11 +42,10 @@ impl TextLogView { // Start pulling only if the query did not finished, i.e. we don't know the end time. // (but respect the FLUSH_INTERVAL_MILLISECONDS) let now = Local::now(); - if max_query_end_microseconds.is_some() - && ((now - max_query_end_microseconds.unwrap()) >= flush_interval_milliseconds + if let Some(mut max_query_end_microseconds) = max_query_end_microseconds + && ((now - max_query_end_microseconds) >= flush_interval_milliseconds || query_ids.is_none()) { - let mut max_query_end_microseconds = max_query_end_microseconds.unwrap(); // It is possible to have messages in the system.text_log, whose // event_time_microseconds > max(event_time_microseconds) from system.query_log // But let's consider that 3 seconds is enough.