Skip to content

Commit

Permalink
feat: document & streamline flight SQL CLI
Browse files Browse the repository at this point in the history
- add docs to README
- add a few more clap features
- document arguments
- unify key-value parsing for headers and parameters
  • Loading branch information
crepererum committed Oct 10, 2023
1 parent 16f5905 commit 1f24245
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 50 deletions.
2 changes: 1 addition & 1 deletion arrow-flight/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ tonic = { version = "0.10.0", default-features = false, features = ["transport",

# CLI-related dependencies
anyhow = { version = "1.0", optional = true }
clap = { version = "4.1", default-features = false, features = ["std", "derive", "env", "help", "error-context", "usage"], optional = true }
clap = { version = "4.4.6", default-features = false, features = ["std", "derive", "env", "help", "error-context", "usage", "wrap_help", "color", "suggestions"], optional = true }
tracing-log = { version = "0.1", optional = true }
tracing-subscriber = { version = "0.3.1", default-features = false, features = ["ansi", "env-filter", "fmt"], optional = true }

Expand Down
35 changes: 33 additions & 2 deletions arrow-flight/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ See the [API documentation](https://docs.rs/arrow_flight/latest) for examples an

The API documentation for most recent, unreleased code is available [here](https://arrow.apache.org/rust/arrow_flight/index.html).


## Usage

Add this to your Cargo.toml:
Expand All @@ -41,8 +42,38 @@ This crate provides a Rust implementation of the
[examples](https://github.com/apache/arrow-rs/tree/master/arrow-flight/examples)
that demonstrate how to build a Flight server implemented with [tonic](https://docs.rs/crate/tonic/latest).


## Feature Flags

- `flight-sql-experimental`: Enables experimental support for
[Apache Arrow FlightSQL](https://arrow.apache.org/docs/format/FlightSql.html),
a protocol for interacting with SQL databases.
[Apache Arrow FlightSQL], a protocol for interacting with SQL databases.


## CLI
This crates offers a basic [Apache Arrow FlightSQL] command line interface.

The client can be installed from the repository:

```console
$ cargo install --features=cli,flight-sql-experimental,tls --bin=flight_sql_client --path=. --locked
```

The client comes with extensive help text:

```console
$ flight_sql_client help
```

A query can be executed using:

```console
$ flight_sql_client --host example.com statement-query "SELECT 1;"
+----------+
| Int64(1) |
+----------+
| 1 |
+----------+
```


[Apache Arrow FlightSQL]: https://arrow.apache.org/docs/format/FlightSql.html
97 changes: 50 additions & 47 deletions arrow-flight/src/bin/flight_sql_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use std::{error::Error, sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration};

use anyhow::{bail, Context, Result};
use arrow_array::{ArrayRef, Datum, RecordBatch, StringArray};
Expand All @@ -30,45 +30,17 @@ use tonic::{
};
use tracing_log::log::info;

/// A ':' separated key value pair
#[derive(Debug, Clone)]
struct KeyValue<K, V> {
pub key: K,
pub value: V,
}

impl<K, V> std::str::FromStr for KeyValue<K, V>
where
K: std::str::FromStr,
V: std::str::FromStr,
K::Err: std::fmt::Display,
V::Err: std::fmt::Display,
{
type Err = String;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let parts = s.splitn(2, ':').collect::<Vec<_>>();
match parts.as_slice() {
[key, value] => {
let key = K::from_str(key).map_err(|e| e.to_string())?;
let value = V::from_str(value.trim()).map_err(|e| e.to_string())?;
Ok(Self { key, value })
}
_ => Err(format!(
"Invalid key value pair - expected 'KEY:VALUE' got '{s}'"
)),
}
}
}

/// Logging CLI config.
#[derive(Debug, Parser)]
pub struct LoggingArgs {
/// Log verbosity.
///
/// Use `-v for warn, `-vv for info, -vvv for debug, -vvvv for trace.
/// Defaults to "warn".
///
/// Note you can also set logging level using `RUST_LOG` environment variable: `RUST_LOG=debug`
/// Use `-v` for "info", `-vv` for "debug", `-vvv` for "trace".
///
/// Note you can also set logging level using `RUST_LOG` environment variable:
/// `RUST_LOG=debug`.
#[clap(
short = 'v',
long = "verbose",
Expand All @@ -81,31 +53,43 @@ pub struct LoggingArgs {
struct ClientArgs {
/// Additional headers.
///
/// Values should be key value pairs separated by ':'
#[clap(long, value_delimiter = ',')]
headers: Vec<KeyValue<String, String>>,
/// Can be given multiple times. Headers and values are separated by '='.
///
/// Example: `-h foo=bar -h baz=42`
#[clap(short, value_parser = parse_key_val)]
headers: Vec<(String, String)>,

/// Username
#[clap(long)]
/// Username.
///
/// Optional. If given, `password` must also be set.
#[clap(long, requires = "password")]
username: Option<String>,

/// Password
#[clap(long)]
/// Password.
///
/// Optional. If given, `username` must also be set.
#[clap(long, requires = "username")]
password: Option<String>,

/// Auth token.
#[clap(long)]
token: Option<String>,

/// Use TLS.
///
/// If not provided, use cleartext connection.
#[clap(long)]
tls: bool,

/// Server host.
///
/// Required.
#[clap(long)]
host: String,

/// Server port.
///
/// Defaults to `443` if `tls` is set, otherwise defaults to `80`.
#[clap(long)]
port: Option<u16>,
}
Expand All @@ -124,13 +108,34 @@ struct Args {
cmd: Command,
}

/// Different available commands.
#[derive(Debug, Subcommand)]
enum Command {
/// Execute given statement.
StatementQuery {
/// SQL query.
///
/// Required.
query: String,
},

/// Prepare given statement and then execute it.
PreparedStatementQuery {
/// SQL query.
///
/// Required.
///
/// Can contains placeholders like `$1`.
///
/// Example: `SELECT * FROM t WHERE x = $1`
query: String,

/// Additional parameters.
///
/// Can be given multiple times. Names and values are separated by '='. Values will be
/// converted to the type that the server reported for the prepared statement.
///
/// Example: `-p $1=42`
#[clap(short, value_parser = parse_key_val)]
params: Vec<(String, String)>,
},
Expand Down Expand Up @@ -284,8 +289,8 @@ async fn setup_client(args: ClientArgs) -> Result<FlightSqlServiceClient<Channel
let mut client = FlightSqlServiceClient::new(channel);
info!("connected");

for kv in args.headers {
client.set_header(kv.key, kv.value);
for (k, v) in args.headers {
client.set_header(k, v);
}

if let Some(token) = args.token {
Expand Down Expand Up @@ -314,13 +319,11 @@ async fn setup_client(args: ClientArgs) -> Result<FlightSqlServiceClient<Channel
}

/// Parse a single key-value pair
fn parse_key_val(
s: &str,
) -> Result<(String, String), Box<dyn Error + Send + Sync + 'static>> {
fn parse_key_val(s: &str) -> Result<(String, String), String> {
let pos = s
.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
Ok((s[..pos].to_owned(), s[pos + 1..].to_owned()))
}

/// Log headers/trailers.
Expand Down

0 comments on commit 1f24245

Please sign in to comment.