This is an automated email from the ASF dual-hosted git repository.
alamb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/master by this push:
new 4aabd2c5de feat: document & streamline flight SQL CLI (#4912)
4aabd2c5de is described below
commit 4aabd2c5ded46d529cce0714a776f0a6336a9a89
Author: Marco Neumann <[email protected]>
AuthorDate: Tue Oct 10 20:14:14 2023 +0200
feat: document & streamline flight SQL CLI (#4912)
- add docs to README
- add a few more clap features
- document arguments
- unify key-value parsing for headers and parameters
---
arrow-flight/Cargo.toml | 2 +-
arrow-flight/README.md | 32 +++++++++-
arrow-flight/src/bin/flight_sql_client.rs | 97 ++++++++++++++++---------------
3 files changed, 81 insertions(+), 50 deletions(-)
diff --git a/arrow-flight/Cargo.toml b/arrow-flight/Cargo.toml
index edaa7129dc..70227eedea 100644
--- a/arrow-flight/Cargo.toml
+++ b/arrow-flight/Cargo.toml
@@ -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 }
diff --git a/arrow-flight/README.md b/arrow-flight/README.md
index 9194b209fe..b80772ac92 100644
--- a/arrow-flight/README.md
+++ b/arrow-flight/README.md
@@ -44,5 +44,33 @@ that demonstrate how to build a Flight server implemented
with [tonic](https://d
## 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
diff --git a/arrow-flight/src/bin/flight_sql_client.rs
b/arrow-flight/src/bin/flight_sql_client.rs
index df51530b3c..296efc1c30 100644
--- a/arrow-flight/src/bin/flight_sql_client.rs
+++ b/arrow-flight/src/bin/flight_sql_client.rs
@@ -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};
@@ -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",
@@ -81,16 +53,22 @@ 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(long = "header", short = 'H', 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.
@@ -98,14 +76,20 @@ struct ClientArgs {
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>,
}
@@ -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)>,
},
@@ -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 {
@@ -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.