diff --git a/client/src/main.rs b/client/src/main.rs index 66b281d..e0fd46b 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,3 +1,4 @@ +use std::io::Write; use clap::Parser; use proto::handshake::client::do_client_handshake; use proto::handshake::request::HandshakeRequest; @@ -5,7 +6,7 @@ use proto::reader::protoreader::ProtoReader; use proto::writer::protowriter::{ProtoFlush, ProtoWriter}; use tokio::io::{BufReader, BufWriter}; use tokio::net::TcpStream; -use proto::message::backend::{BackendMessage, DataRowData, RowDescriptionData}; +use proto::message::backend::{BackendMessage, CommandCompleteData, DataRowData, ErrorResponseData, RowDescriptionData}; use proto::message::frontend::{FrontendMessage, QueryData}; use proto::reader::oneway::OneWayProtoReader; use proto::writer::oneway::OneWayProtoWriter; @@ -19,12 +20,17 @@ struct Cli { /// Host name or IP address of the server. #[arg(long, default_value = "127.0.0.1", help = "Host name or IP address of the server")] host: String, + + /// User name sent to the server. + #[arg(long, default_value = "minisql user", help = "User name")] + username: String, } #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::parse(); let addr = format!("{}:{}", cli.host, cli.port); + println!("Connecting to {}", addr); let mut stream = TcpStream::connect(addr).await?; let (reader, writer) = stream.split(); @@ -33,42 +39,55 @@ async fn main() -> anyhow::Result<()> { let mut reader = ProtoReader::new(BufReader::new(reader), 1024); let request = HandshakeRequest::new(196608) - .parameter("user", "test user") + .parameter("user", cli.username.as_str()) .parameter("client_encoding", "UTF8"); - let response = do_client_handshake(&mut writer, &mut reader, request).await?; + let _ = do_client_handshake(&mut writer, &mut reader, request).await?; + println!("Connected to the server"); - println!("Handshake complete:\n{response:?}"); + let mut exit = false; + let command = prompt()?; + if let Some(cmd) = command { + writer.write_proto(FrontendMessage::Query(QueryData { + query: cmd.into(), + })).await?; + writer.flush().await?; + } else { + exit = true; + } - writer.write_proto(FrontendMessage::Query(QueryData { - query: "SELECT * FROM users;".to_string().into(), - })).await?; - writer.flush().await?; - let mut line = String::new(); - loop { + while !exit { let msg: BackendMessage = reader.read_proto().await?; match msg { BackendMessage::RowDescription(data) => { - print_header(data); + print_row_description(data); }, BackendMessage::DataRow(data) => { - print_row(data); + print_row_data(data); }, BackendMessage::CommandComplete(data) => { - println!("Command complete: {:?}", data); + print_command_complete(data); + }, + BackendMessage::ErrorResponse(data) => { + print_error_response(data); + }, + BackendMessage::EmptyQueryResponse => { + println!("Empty query response"); + }, + BackendMessage::NoData => { + println!("No data"); }, BackendMessage::ReadyForQuery(data) => { - println!("Ready for query: {:?}", data); - line.clear(); - let res = std::io::stdin().read_line(&mut line); - if let Ok(_) = res { - if line.eq("exit") { - break; - } + println!("Ready for next query ({})", data.status); + + let command = prompt()?; + if let Some(cmd) = command { writer.write_proto(FrontendMessage::Query(QueryData { - query: line.clone().into(), + query: cmd.into(), })).await?; writer.flush().await?; + } else { + exit = true; } }, m => { @@ -83,21 +102,70 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn print_header(header: RowDescriptionData) { - print!("Header -> "); - for column in Vec::from(header.columns) { - print!("{} | ", column.name.as_str()); +fn prompt() -> std::io::Result> { + print!("> "); + std::io::stdout().flush()?; + + let mut line = String::new(); + let _ = std::io::stdin().read_line(&mut line)?; + + let line = line.trim(); + + if line.is_empty() { + return prompt(); + } + + if line == "exit" || line == "quit" { + return Ok(None); + } + + Ok(Some(line.trim().to_string())) +} + +fn print_error_response(data: ErrorResponseData) { + println!("Error with code {}: {}", data.code, data.message.as_str()); +} + +fn print_command_complete(data: CommandCompleteData) { + println!("Result: {}", data.tag.as_str()); +} + +fn print_row_description(data: RowDescriptionData) { + let mut lengths = vec![]; + + let columns = Vec::from(data.columns); + for i in 0..columns.len() { + let column_name = columns[i].name.as_str(); + lengths.push(column_name.len()); + print!("{}", column_name); + if i < columns.len() - 1 { + print!(" | "); + } + } + + println!(); + for i in 0..lengths.len() { + for _ in 0..lengths[i] { + print!("-"); + } + if i < lengths.len() - 1 { + print!("-+-"); + } } println!(); } -fn print_row(row: DataRowData) { - print!("Row -> "); - for column in Vec::from(row.columns) { - let bytes = Vec::from(column); +fn print_row_data(data: DataRowData) { + let columns = Vec::from(data.columns); + let length = columns.len(); + for column in columns.into_iter().enumerate() { + let bytes = Vec::from(column.1); let string = String::from_utf8(bytes).unwrap(); - print!("{} | ", string); + print!("{}", string); + if column.0 < length - 1 { + print!(" | "); + } } println!(); }