arrow_cli 0.5.0

A command line tool for interacting with server in Flight SQL protocol
mod helper;
mod output;
mod session;

use std::time::Duration;

use arrow_schema::ArrowError;
use atty::Stream;

use clap::Parser;
use output::Output;
use tonic::transport::{ClientTlsConfig, Endpoint};

#[derive(Debug, Parser, PartialEq)]
#[command(version)]
struct Args {
    #[clap(short = 'u', long, default_value = "root", help = "User name")]
    user: String,

    #[clap(short = 'p', long, default_value = "", help = "User password")]
    password: String,

    #[clap(long, default_value = "127.0.0.1", help = "Flight SQL Server host")]
    host: String,
    #[clap(
        short = 'P',
        long,
        default_value_t = 4100,
        help = "Flight SQL Server port"
    )]
    port: u16,

    #[clap(long)]
    tls: bool,

    #[clap(long, default_value = "180", help = "Request timeout in seconds")]
    timeout: u64,

    #[clap(
        long,
        default_value = "false",
        help = "Execute query using prepared statement"
    )]
    prepared: bool,

    #[clap(long, default_value = "false", help = "Print resultset schema")]
    print_schema: bool,

    #[clap(
        long,
        value_enum,
        default_value_t = Output::Table,
        help = "Result output format"
    )]
    output: Output,

    #[clap(short = 'c', long, help = "Execute SQL command and exit")]
    command: Option<String>,
}

#[tokio::main]
pub async fn main() -> Result<(), ArrowError> {
    let args = Args::parse();

    let protocol = if args.tls { "https" } else { "http" };
    // Authenticate
    let url = format!("{protocol}://{}:{}", args.host, args.port);
    let endpoint = endpoint(&args, url)?;
    let is_repl = atty::is(Stream::Stdin) && args.command.is_none();
    let mut session = session::Session::try_new(endpoint, is_repl, args).await?;

    session.handle().await;
    Ok(())
}

fn endpoint(args: &Args, addr: String) -> Result<Endpoint, ArrowError> {
    let mut endpoint = Endpoint::new(addr)
        .map_err(|_| ArrowError::IpcError("Cannot create endpoint".to_string()))?
        .connect_timeout(Duration::from_secs(20))
        .timeout(Duration::from_secs(args.timeout))
        .tcp_nodelay(true) // Disable Nagle's Algorithm since we don't want packets to wait
        .tcp_keepalive(Some(Duration::from_secs(3600)))
        .http2_keep_alive_interval(Duration::from_secs(300))
        .keep_alive_timeout(Duration::from_secs(20))
        .keep_alive_while_idle(true);

    if args.tls {
        let tls_config = ClientTlsConfig::new();
        endpoint = endpoint
            .tls_config(tls_config)
            .map_err(|_| ArrowError::IpcError("Cannot create TLS endpoint".to_string()))?;
    }

    Ok(endpoint)
}