diff --git a/Cargo.lock b/Cargo.lock index 832c23c9..3065b7dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3524,14 +3524,13 @@ name = "modelardb_client" version = "0.1.0" dependencies = [ "arrow", - "arrow-flight", - "bytes", "clap", "dirs", - "modelardb_auth", + "futures", + "modelardb_embedded", + "modelardb_types", "rustyline", "tokio", - "tonic", ] [[package]] diff --git a/crates/modelardb_client/Cargo.toml b/crates/modelardb_client/Cargo.toml index dd768b13..a861e51c 100644 --- a/crates/modelardb_client/Cargo.toml +++ b/crates/modelardb_client/Cargo.toml @@ -24,12 +24,11 @@ name = "modelardb" path = "src/main.rs" [dependencies] -arrow-flight.workspace = true arrow = { workspace = true, features = ["prettyprint"] } -bytes.workspace = true clap.workspace = true dirs.workspace = true -modelardb_auth.workspace = true +futures.workspace = true +modelardb_embedded.workspace = true +modelardb_types.workspace = true rustyline.workspace = true tokio.workspace = true -tonic.workspace = true diff --git a/crates/modelardb_client/src/error.rs b/crates/modelardb_client/src/error.rs index 9cb489d6..18b59d54 100644 --- a/crates/modelardb_client/src/error.rs +++ b/crates/modelardb_client/src/error.rs @@ -21,9 +21,8 @@ use std::io::Error as IoError; use std::result::Result as StdResult; use arrow::error::ArrowError; +use modelardb_embedded::error::ModelarDbEmbeddedError; use rustyline::error::ReadlineError as RustyLineError; -use tonic::Status as TonicStatusError; -use tonic::transport::Error as TonicTransportError; /// Result type used throughout `modelardb_client`. pub type Result = StdResult; @@ -37,12 +36,10 @@ pub enum ModelarDbClientError { InvalidArgument(String), /// Error returned from IO operations. Io(IoError), + /// Error returned by modelardb_embedded. + ModelarDbEmbedded(ModelarDbEmbeddedError), /// Error returned by RustyLine. RustyLine(RustyLineError), - /// Status returned by Tonic. - TonicStatus(Box), - /// Error returned by Tonic. - TonicTransport(TonicTransportError), } impl Display for ModelarDbClientError { @@ -51,9 +48,8 @@ impl Display for ModelarDbClientError { Self::Arrow(reason) => write!(f, "Arrow Error: {reason}"), Self::InvalidArgument(reason) => write!(f, "Invalid Argument Error: {reason}"), Self::Io(reason) => write!(f, "Io Error: {reason}"), + Self::ModelarDbEmbedded(reason) => write!(f, "ModelarDB Embedded Error: {reason}"), Self::RustyLine(reason) => write!(f, "RustyLine Error: {reason}"), - Self::TonicStatus(reason) => write!(f, "Tonic Status Error: {reason}"), - Self::TonicTransport(reason) => write!(f, "Tonic Transport Error: {reason}"), } } } @@ -65,9 +61,8 @@ impl Error for ModelarDbClientError { Self::Arrow(reason) => Some(reason), Self::InvalidArgument(_reason) => None, Self::Io(reason) => Some(reason), + Self::ModelarDbEmbedded(reason) => Some(reason), Self::RustyLine(reason) => Some(reason), - Self::TonicStatus(reason) => Some(reason), - Self::TonicTransport(reason) => Some(reason), } } } @@ -84,20 +79,14 @@ impl From for ModelarDbClientError { } } -impl From for ModelarDbClientError { - fn from(error: RustyLineError) -> Self { - Self::RustyLine(error) +impl From for ModelarDbClientError { + fn from(error: ModelarDbEmbeddedError) -> Self { + Self::ModelarDbEmbedded(error) } } -impl From for ModelarDbClientError { - fn from(error: TonicStatusError) -> Self { - Self::TonicStatus(Box::new(error)) - } -} - -impl From for ModelarDbClientError { - fn from(error: TonicTransportError) -> Self { - Self::TonicTransport(error) +impl From for ModelarDbClientError { + fn from(error: RustyLineError) -> Self { + Self::RustyLine(error) } } diff --git a/crates/modelardb_client/src/main.rs b/crates/modelardb_client/src/main.rs index 0cab02b0..d5b5b1f4 100644 --- a/crates/modelardb_client/src/main.rs +++ b/crates/modelardb_client/src/main.rs @@ -18,40 +18,26 @@ mod error; mod helper; -use std::collections::HashMap; -use std::convert::TryFrom; use std::fs::File; use std::io::{self, BufRead, BufReader, IsTerminal, Write}; use std::path::{Path as StdPath, PathBuf}; use std::process; -use std::sync::Arc; use std::time::Instant; -use arrow::array::ArrayRef; -use arrow::datatypes::Schema; -use arrow::ipc::convert; use arrow::util::pretty; -use arrow_flight::flight_service_client::FlightServiceClient; -use arrow_flight::{Action, Criteria, FlightData, FlightDescriptor, Ticket, utils}; -use bytes::Bytes; use clap::Parser; -use modelardb_auth::BearerInterceptor; +use futures::StreamExt; +use modelardb_embedded::error::ModelarDbEmbeddedError; +use modelardb_embedded::operations::Operations; +use modelardb_embedded::operations::client::Client; +use modelardb_types::flight::protocol; +use modelardb_types::flight::protocol::update_configuration::Setting; use rustyline::Editor; use rustyline::history::FileHistory; -use tonic::codegen::InterceptedService; -use tonic::transport::{Channel, Endpoint}; -use tonic::{Request, Streaming}; use crate::error::{ModelarDbClientError, Result}; use crate::helper::ClientHelper; -/// Error to emit when the server does not provide a response when one is expected. -const TRANSPORT_ERROR: &str = "transport error: no messages received."; - -/// [`FlightServiceClient`] with a [`BearerInterceptor`] that attaches an authorization header. -type AuthenticatedFlightClient = - FlightServiceClient>; - /// Command line arguments for the ModelarDB client. #[derive(Parser)] #[command( @@ -88,39 +74,20 @@ async fn main() -> Result<()> { // Parse the command line arguments. let args = ClientArgs::parse(); + // Connect to the server. + let url = format!("grpc://{}:{}", args.host, args.port); + let client = Client::connect(&url, args.token.as_deref()).await?; + // Execute the queries. - let flight_service_client = connect(&args.host, args.port, args.token).await?; if let Some(query_file) = args.query_file { - execute_queries_from_a_file(flight_service_client, &query_file).await + execute_queries_from_a_file(client, &query_file).await } else { - execute_queries_from_a_repl(flight_service_client).await + execute_queries_from_a_repl(client).await } } -/// Connect to the server at `host`:`port` with an optional bearer `maybe_token`. Returns -/// [`ModelarDbClientError`] if a connection to the server cannot be established or the token is -/// not a valid ASCII metadata value. -async fn connect( - host: &str, - port: u16, - maybe_token: Option, -) -> Result { - let interceptor = BearerInterceptor::try_new(maybe_token.as_deref())?; - - let address = format!("grpc://{host}:{port}"); - let connection = Endpoint::new(address)?.connect().await?; - - Ok(FlightServiceClient::with_interceptor( - connection, - interceptor, - )) -} - /// Execute the commands and queries in `query_file`. -async fn execute_queries_from_a_file( - mut flight_service_client: AuthenticatedFlightClient, - query_file: &StdPath, -) -> Result<()> { +async fn execute_queries_from_a_file(mut client: Client, query_file: &StdPath) -> Result<()> { let file = File::open(query_file)?; let lines = BufReader::new(file).lines(); @@ -134,9 +101,9 @@ async fn execute_queries_from_a_file( }; // Execute the query. - if !query.is_empty() { + if !query.trim().is_empty() { println!("{query}"); - execute_and_print_command_or_query(&mut flight_service_client, &query).await + execute_and_print_command_or_query(&mut client, &query).await; } } @@ -144,13 +111,10 @@ async fn execute_queries_from_a_file( } /// Execute commands and queries in a read-eval-print loop. -async fn execute_queries_from_a_repl( - mut flight_service_client: AuthenticatedFlightClient, -) -> Result<()> { +async fn execute_queries_from_a_repl(mut client: Client) -> Result<()> { // Create the read-eval-print loop. let mut editor = Editor::::new()?; - let table_names = retrieve_table_names(&mut flight_service_client).await?; - editor.set_helper(Some(ClientHelper::new(table_names))); + editor.set_helper(Some(ClientHelper::new(client.tables().await?))); // Read previously executed commands and queries from the history file. let history_file_name = ".modelardb_history"; @@ -165,7 +129,15 @@ async fn execute_queries_from_a_repl( // Execute commands and queries and print the result. while let Ok(line) = editor.readline("ModelarDB> ") { editor.add_history_entry(line.as_str())?; - execute_and_print_command_or_query(&mut flight_service_client, &line).await + execute_and_print_command_or_query(&mut client, &line).await; + + // Refresh the table names for tab-completion if a table may have been created or dropped. + let first_word = line.split_whitespace().next().unwrap_or(""); + if (first_word.eq_ignore_ascii_case("CREATE") || first_word.eq_ignore_ascii_case("DROP")) + && let Ok(table_names) = client.tables().await + { + editor.set_helper(Some(ClientHelper::new(table_names))); + } } // Append the executed commands and queries to the history file. @@ -179,17 +151,19 @@ async fn execute_queries_from_a_repl( /// Execute a command or a query. Returns [`ModelarDbClientError`] if the command or query could not /// be executed or their result could not be retrieved. -async fn execute_and_print_command_or_query( - flight_service_client: &mut AuthenticatedFlightClient, - command_or_query: &str, -) { +async fn execute_and_print_command_or_query(client: &mut Client, command_or_query: &str) { let start_time = Instant::now(); let command_or_query = command_or_query.trim(); + // Nothing to execute if Enter was pressed without any input. + if command_or_query.is_empty() { + return; + } + let result = if command_or_query.starts_with('\\') { - execute_command(flight_service_client, command_or_query).await + execute_command(client, command_or_query).await } else { - execute_query_and_print_result(flight_service_client, command_or_query).await + execute_query_and_print_result(client, command_or_query).await }; if let Err(message) = result { @@ -203,12 +177,9 @@ async fn execute_and_print_command_or_query( /// * An incorrect argument for the command was provided. /// * The command could not be executed. /// * The result could not be retrieved. -async fn execute_command( - flight_service_client: &mut AuthenticatedFlightClient, - command_and_argument: &str, -) -> Result<()> { - let mut command_and_argument = command_and_argument.split(' '); - match command_and_argument +async fn execute_command(client: &mut Client, command_and_arguments: &str) -> Result<()> { + let mut command_and_arguments = command_and_arguments.split_whitespace(); + match command_and_arguments .next() .ok_or(ModelarDbClientError::InvalidArgument( "No command was provided.".to_owned(), @@ -216,18 +187,13 @@ async fn execute_command( // Print the schema of a table on the server. "\\d" => { let table_name = - command_and_argument + command_and_arguments .next() .ok_or(ModelarDbClientError::InvalidArgument( "No table name was provided.".to_owned(), ))?; - let flight_descriptor = FlightDescriptor::new_path(vec![table_name.to_owned()]); - let request = Request::new(flight_descriptor); - let schema_result = flight_service_client - .get_schema(request) - .await? - .into_inner(); - let schema = convert::try_schema_from_ipc_buffer(&schema_result.schema)?; + + let schema = client.schema(table_name).await?; for field in schema.fields() { print!("{}: {}", field.name(), field.data_type()); for (metadata_name, metadata_value) in field.metadata() { @@ -239,32 +205,85 @@ async fn execute_command( } // Print the name of the tables on the server. "\\dt" => { - if let Ok(tables) = retrieve_table_names(flight_service_client).await { - for table in tables { - println!("{table}"); - } + for table_name in client.tables().await? { + println!("{table_name}"); + } + Ok(()) + } + // Print the configuration of the node. + "\\dc" => { + print_configuration(&client.configuration().await?); + Ok(()) + } + // Print the nodes that are currently part of the cluster. + "\\dn" => { + for node in client.list_nodes().await? { + println!("{} ({})", node.url, node.mode); } Ok(()) } + // Print the resource usage metrics of the node. + "\\dm" => { + print_node_metrics(&client.node_metrics().await?); + Ok(()) + } + // Update a setting in the configuration of the node. + "\\sc" => { + let name = + command_and_arguments + .next() + .ok_or(ModelarDbClientError::InvalidArgument( + "No setting was provided.".to_owned(), + ))?; + + let setting = Setting::from_str_name(&name.to_uppercase()).ok_or( + ModelarDbClientError::InvalidArgument(format!("Unknown setting: {name}.")), + )?; + + // Omitting the value unsets the setting if it is optional. + let maybe_new_value = match command_and_arguments.next() { + Some(value) => Some(value.parse::().map_err(|_error| { + ModelarDbClientError::InvalidArgument(format!( + "{value} is not a valid value for {name}." + )) + })?), + None => None, + }; + + client + .update_configuration(setting, maybe_new_value) + .await?; + Ok(()) + } // Flushes all data the server currently has in memory to disk. - "\\f" => execute_action(flight_service_client, "FlushMemory", "").await, + "\\f" => client.flush_memory().await.map_err(|error| error.into()), // Flushes all data the server currently has in memory and disk to the object store. - "\\F" => execute_action(flight_service_client, "FlushNode", "").await, + "\\F" => client.flush_node().await.map_err(|error| error.into()), // Print helpful information, explanations with \\ must be indented more to be aligned. "\\h" => { println!( "CREATE [TIME SERIES] TABLE Execute a CREATE TABLE or CREATE TIME SERIES TABLE statement.\n\ INSERT INTO Execute an INSERT INTO statement. Must include generated columns.\n\ SELECT Execute a SELECT statement.\n\ - \\d TABLE_NAME Print the schema of a table with TABLE_NAME.\n\ - \\dt Print the name of all the tables.\n\ - \\f Flushes data in memory to disk.\n\ - \\F Flushes data in memory and disk to the object store.\n\ - \\h Print documentation for all supported commands.\n\ - \\q Quit modelardb." + \\d TABLE_NAME Print the schema of a table with TABLE_NAME.\n\ + \\dt Print the name of all the tables.\n\ + \\dc Print the configuration of the node.\n\ + \\dn Print the nodes in the cluster.\n\ + \\dm Print the resource usage metrics of the node.\n\ + \\sc SETTING [VALUE] Set SETTING to VALUE, or unset SETTING if VALUE is omitted.\n\ + \\f Flushes data in memory to disk.\n\ + \\F Flushes data in memory and disk to the object store.\n\ + \\h Print documentation for all supported commands.\n\ + \\k Flush data to disk, kill the node, and quit modelardb.\n\ + \\q Quit modelardb." ); Ok(()) } + // Kill the node and quit, the connection is dead once the node process exits. + "\\k" => { + client.kill_node().await?; + process::exit(0); + } "\\q" => { process::exit(0); } @@ -274,140 +293,115 @@ async fn execute_command( } } -/// Retrieve the names of the tables available on the server. Returns [`ModelarDbClientError`] if -/// the request could not be performed or the tables names could not be retrieved. -async fn retrieve_table_names( - flight_service_client: &mut AuthenticatedFlightClient, -) -> Result> { - let criteria = Criteria { - expression: Bytes::new(), - }; - let request = Request::new(criteria); - - let mut stream = flight_service_client - .list_flights(request) - .await? - .into_inner(); +/// Execute a query and print each batch in the result set. If standard output is a terminal, ask +/// the user for confirmation before printing each batch after the first. Returns +/// [`ModelarDbClientError`] if the query could not be executed or the batches in the result set +/// could not be printed. +async fn execute_query_and_print_result(client: &mut Client, query: &str) -> Result<()> { + let mut record_batch_stream = client.read(query).await?; - let flight_infos = stream - .message() - .await? - .ok_or(ModelarDbClientError::InvalidArgument( - TRANSPORT_ERROR.to_owned(), - ))?; + let print_confirmation = io::stdout().is_terminal(); + let mut multiple_batches = false; - let mut table_names = vec![]; - if let Some(flight_descriptor) = flight_infos.flight_descriptor { - for table_name in flight_descriptor.path { - table_names.push(table_name); + while let Some(record_batch) = record_batch_stream.next().await { + // Only ask for confirmation to print the next batch if there are multiple batches. + if print_confirmation && multiple_batches && !confirm_printing_next_batch()? { + return Ok(()); } - } - - Ok(table_names) -} -/// Execute an action. Returns [`ModelarDbClientError`] if the action could not be executed. -async fn execute_action( - flight_service_client: &mut AuthenticatedFlightClient, - action_type: &str, - action_body: &str, -) -> Result<()> { - let action = Action { - r#type: action_type.to_owned(), - body: action_body.to_owned().into(), - }; - - let request = Request::new(action); - - flight_service_client - .do_action(request) - .await? - .into_inner() - .message() - .await?; + let record_batch = record_batch.map_err(ModelarDbEmbeddedError::from)?; + pretty::print_batches(&[record_batch])?; + multiple_batches = true; + } Ok(()) } -/// Execute a query and print each batch in the result set. Returns [`ModelarDbClientError`] if the -/// query could not be executed or the batches in the result set could not be printed. -async fn execute_query_and_print_result( - flight_service_client: &mut AuthenticatedFlightClient, - query: &str, -) -> Result<()> { - // Execute the query. - let ticket = Ticket { - ticket: query.to_owned().into(), - }; - let mut stream = flight_service_client.do_get(ticket).await?.into_inner(); - - // Get the schema of the data in the query result. - let flight_data = stream - .message() - .await? - .ok_or(ModelarDbClientError::InvalidArgument( - TRANSPORT_ERROR.to_owned(), - ))?; - let schema = Arc::new(Schema::try_from(&flight_data)?); - let dictionaries_by_id = HashMap::new(); - - if io::stdout().is_terminal() { - print_batches_with_confirmation(stream, schema, &dictionaries_by_id).await - } else { - print_batches_without_confirmation(stream, schema, &dictionaries_by_id).await - } -} - -/// Print each batch in the result set with confirmation from the user before printing each batch. -/// Returns [`ModelarDbClientError`] if the batches in the result set could not be printed. -async fn print_batches_with_confirmation( - mut stream: Streaming, - schema: Arc, - dictionaries_by_id: &HashMap, -) -> Result<()> { +/// Ask the user for confirmation before printing the next batch in a result set. Returns false if +/// the user chose to stop printing batches. Returns [`ModelarDbClientError`] if the input could not +/// be read. +fn confirm_printing_next_batch() -> Result { let mut user_input = String::new(); - let mut multiple_batches = false; - while let Some(flight_data) = stream.message().await? { - let record_batch = - utils::flight_data_to_arrow_batch(&flight_data, schema.clone(), dictionaries_by_id)?; + loop { + user_input.clear(); + print!("Press Enter for next batch and q+Enter to quit> "); + io::stdout().flush()?; - // Only ask for confirmation to print the next batch if there are multiple batches. - if multiple_batches { - loop { - user_input.clear(); - print!("Press Enter for next batch and q+Enter to quit> "); - io::stdout().flush()?; - io::stdin().read_line(&mut user_input)?; - - match user_input.as_str() { - "\n" => break, - "q\n" => return Ok(()), - _ => (), - } - } + // A read of zero bytes means standard input reached end-of-file, so no more batches can be + // confirmed. + if io::stdin().read_line(&mut user_input)? == 0 { + return Ok(false); } - pretty::print_batches(&[record_batch])?; - multiple_batches = true; + // The line includes the line ending, which is \r\n on Windows and \n everywhere else. + match user_input.trim() { + "" => return Ok(true), + "q" => return Ok(false), + _ => (), + } } - - Ok(()) } -/// Print each batch in the result set without user input. Returns [`ModelarDbClientError`] if the -/// batches in the result set could not be printed. -async fn print_batches_without_confirmation( - mut stream: Streaming, - schema: Arc, - dictionaries_by_id: &HashMap, -) -> Result<()> { - while let Some(flight_data) = stream.message().await? { - let record_batch = - utils::flight_data_to_arrow_batch(&flight_data, schema.clone(), dictionaries_by_id)?; - - pretty::print_batches(&[record_batch])?; - } +/// Print each field in `configuration` on its own line. +fn print_configuration(configuration: &protocol::Configuration) { + let protocol::Configuration { + ingested_reserved_memory_in_bytes, + uncompressed_reserved_memory_in_bytes, + compressed_reserved_memory_in_bytes, + transfer_batch_size_in_bytes, + segment_size_threshold_in_bytes, + optimize_target_file_size_in_bytes, + vacuum_retention_period_in_seconds, + ingestion_threads, + compression_threads, + writer_threads, + wal_enabled, + } = configuration; + + let transfer_batch_size_in_bytes = + transfer_batch_size_in_bytes.map_or("not set".to_owned(), |value| value.to_string()); + + println!("ingested_reserved_memory_in_bytes: {ingested_reserved_memory_in_bytes}"); + println!("uncompressed_reserved_memory_in_bytes: {uncompressed_reserved_memory_in_bytes}"); + println!("compressed_reserved_memory_in_bytes: {compressed_reserved_memory_in_bytes}"); + println!("transfer_batch_size_in_bytes: {transfer_batch_size_in_bytes}"); + println!("segment_size_threshold_in_bytes: {segment_size_threshold_in_bytes}"); + println!("optimize_target_file_size_in_bytes: {optimize_target_file_size_in_bytes}"); + println!("vacuum_retention_period_in_seconds: {vacuum_retention_period_in_seconds}"); + println!("ingestion_threads: {ingestion_threads}"); + println!("compression_threads: {compression_threads}"); + println!("writer_threads: {writer_threads}"); + println!("wal_enabled: {wal_enabled}"); +} - Ok(()) +/// Print each field in `node_metrics` on its own line. +fn print_node_metrics(node_metrics: &protocol::NodeMetrics) { + let protocol::NodeMetrics { + cpu_usage_percentage, + cpu_count, + used_memory_in_bytes, + total_memory_in_bytes, + used_disk_space_in_bytes, + total_disk_space_in_bytes, + ingested_used_memory_in_bytes, + ingested_reserved_memory_in_bytes, + uncompressed_used_memory_in_bytes, + uncompressed_reserved_memory_in_bytes, + compressed_used_memory_in_bytes, + compressed_reserved_memory_in_bytes, + } = node_metrics; + + println!("cpu_usage_percentage: {cpu_usage_percentage}"); + println!("cpu_count: {cpu_count}"); + println!("used_memory_in_bytes: {used_memory_in_bytes}"); + println!("total_memory_in_bytes: {total_memory_in_bytes}"); + println!("used_disk_space_in_bytes: {used_disk_space_in_bytes}"); + println!("total_disk_space_in_bytes: {total_disk_space_in_bytes}"); + println!("ingested_used_memory_in_bytes: {ingested_used_memory_in_bytes}"); + println!("ingested_reserved_memory_in_bytes: {ingested_reserved_memory_in_bytes}"); + println!("uncompressed_used_memory_in_bytes: {uncompressed_used_memory_in_bytes}"); + println!("uncompressed_reserved_memory_in_bytes: {uncompressed_reserved_memory_in_bytes}"); + println!("compressed_used_memory_in_bytes: {compressed_used_memory_in_bytes}"); + println!("compressed_reserved_memory_in_bytes: {compressed_reserved_memory_in_bytes}"); } diff --git a/crates/modelardb_embedded/src/operations/client.rs b/crates/modelardb_embedded/src/operations/client.rs index 642bafd6..5d92c7c8 100644 --- a/crates/modelardb_embedded/src/operations/client.rs +++ b/crates/modelardb_embedded/src/operations/client.rs @@ -112,11 +112,11 @@ impl Client { Ok(()) } - /// Flushes all data to disk, transfers it to the remote object store, removes the node from the - /// cluster if necessary, and kills the node process. Since the process is killed, a - /// conventional response cannot be returned, so a dropped connection is not treated as an - /// error. If the data could not be flushed before the node was killed, - /// [`ModelarDbEmbeddedError`] is returned. + /// Flushes all data to disk, removes the node from the cluster if necessary, and kills the node + /// process. Data is not transferred to the remote object store. Call [`Client::flush_node`] + /// first if that is required. The node exits while handling the request, so a response cannot + /// be returned. Errors are therefore ignored and [`Ok`] is returned even if the node rejected + /// the request or failed to flush its data. pub async fn kill_node(&mut self) -> Result<()> { // The node exits while handling this action, so the response stream is dropped. let _ = self.send_action("KillNode", vec![]).await; diff --git a/crates/modelardb_server/src/remote/mod.rs b/crates/modelardb_server/src/remote/mod.rs index 65c776d4..0fae3abf 100644 --- a/crates/modelardb_server/src/remote/mod.rs +++ b/crates/modelardb_server/src/remote/mod.rs @@ -886,10 +886,11 @@ impl FlightService for FlightServiceHandler { /// currently in memory to disk and then flushes all compressed data on disk to the remote /// object store. Note that data is only transferred to the remote object store if one was /// provided when starting the server. - /// * `KillNode`: An extension of the `FlushNode` action that first flushes all data to disk, - /// then flushes all compressed data to the remote object store, then removes the node - /// from the cluster if necessary, and finally kills the process that is running the server. - /// Note that since the process is killed, a conventional response cannot be returned. + /// * `KillNode`: An extension of the `FlushMemory` action that first flushes all data that is + /// currently in memory to disk, then removes the node from the cluster if necessary, and + /// finally kills the process that is running the server. Data is not transferred to the remote + /// object store. Use `FlushNode` first if that is required. Note that since the process is + /// killed, a conventional response cannot be returned. /// * `GetConfiguration`: Get the current server configuration. The value of each setting in the /// configuration is returned in a [`Configuration`](protocol::Configuration) protobuf message. /// * `UpdateConfiguration`: Update a single setting in the configuration. The setting to update @@ -954,13 +955,11 @@ impl FlightService for FlightServiceHandler { // Confirm the data was flushed. Ok(Response::new(Box::pin(stream::empty()))) } else if action.r#type == "KillNode" { - let mut storage_engine = self.context.storage_engine.write().await; - storage_engine - .flush() + self.context + .storage_engine + .write() .await - .map_err(error_to_status_internal)?; - storage_engine - .transfer() + .flush() .await .map_err(error_to_status_internal)?; @@ -1135,8 +1134,9 @@ impl FlightService for FlightServiceHandler { let kill_node_action = ActionType { r#type: "KillNode".to_owned(), description: "Flush uncompressed data to disk by compressing and saving the data, \ - transfer all compressed data to the remote object store, and kill the \ - process running the server." + remove the node from the cluster if necessary, and kill the process \ + running the server. Data is not transferred to the remote object store. \ + Use FlushNode first if that is required." .to_owned(), }; diff --git a/docs/user/README.md b/docs/user/README.md index 8a64f20f..e2d5bdce 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -263,10 +263,20 @@ instance executes the `SELECT` statement on the data it manages and forwards the the provided addresses. Afterwards, the `modelardbd` instance that initially received the query, unions the result from all `modelardbd` instances and returns it to the client. As an example, this can be used to execute a `SELECT` statement on both the data in the cloud and a specific edge node. Although, be aware that this does not make the edge node -transfer the data it manages to the cloud, only the result of the query. In addition to SQL statements, the REPL also -supports listing all tables using `\dt`, printing the schema of a table using `\d table_name`, flushing data in memory -to disk using `\f`, flushing data in memory and on disk to an object store using `\F`, and printing operations supported -by the client using `\h`. +transfer the data it manages to the cloud, only the result of the query. + +In addition to SQL statements, the REPL supports the following commands: +- `\d table_name` - Print the schema of the table with `table_name`. +- `\dt` - Print the name of all the tables. +- `\dc` - Print the configuration of the node. +- `\dn` - Print the nodes in the cluster. +- `\dm` - Print the resource usage metrics of the node. +- `\sc setting [value]` - Set `setting` to `value`, or unset `setting` if `value` is omitted. +- `\f` - Flush data in memory to disk. +- `\F` - Flush data in memory and on disk to an object store. +- `\h` - Print documentation for all supported commands. +- `\k` - Flush data to disk, kill the node, and quit `modelardb`. +- `\q` - Quit `modelardb`. ```sql ModelarDB> \dt