Skip to content

Commit 69c56c1

Browse files
committed
feat: add file output
1 parent ee8301e commit 69c56c1

File tree

4 files changed

+315
-41
lines changed

4 files changed

+315
-41
lines changed

src/cli.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct DatabaseConfig {
2222
pub options: Vec<(String, String)>,
2323
pub query_source: QuerySource,
2424
pub table_mode: TableMode,
25+
pub output_path: Option<PathBuf>,
2526
}
2627

2728
fn is_stdin_piped() -> bool {
@@ -60,6 +61,10 @@ pub fn parse_args() -> DatabaseConfig {
6061
.long("file")
6162
.help("Read and execute file and exit")
6263
.conflicts_with("query"),
64+
Arg::new("output")
65+
.long("output")
66+
.help("Write result to file")
67+
.value_name("file"),
6368
];
6469
let command = Command::new("adbcli")
6570
.version(env!("CARGO_PKG_VERSION"))
@@ -114,6 +119,12 @@ pub fn parse_args() -> DatabaseConfig {
114119
None => TableMode::default(),
115120
};
116121

122+
let output_path = matches.get_one::<String>("output").map(PathBuf::from);
123+
if output_path.is_some() && matches!(query_source, QuerySource::Interactive) {
124+
eprintln!("Error: --output cannot be used in interactive mode");
125+
exit(1);
126+
}
127+
117128
DatabaseConfig {
118129
driver_name,
119130
uri,
@@ -122,6 +133,7 @@ pub fn parse_args() -> DatabaseConfig {
122133
options,
123134
query_source,
124135
table_mode,
136+
output_path,
125137
}
126138
}
127139

@@ -185,6 +197,7 @@ mod tests {
185197
options: vec![("key1".to_string(), "val1".to_string())],
186198
query_source: QuerySource::Interactive,
187199
table_mode: TableMode::default(),
200+
output_path: None,
188201
};
189202

190203
assert_eq!(config.driver_name, "test_driver");
@@ -205,6 +218,7 @@ mod tests {
205218
options: vec![],
206219
query_source: QuerySource::Interactive,
207220
table_mode: TableMode::AsciiMarkdown,
221+
output_path: None,
208222
};
209223

210224
assert_eq!(config.driver_name, "test_driver");
@@ -214,4 +228,20 @@ mod tests {
214228
assert!(config.options.is_empty());
215229
assert_eq!(config.table_mode, TableMode::AsciiMarkdown);
216230
}
231+
232+
#[test]
233+
fn test_database_config_with_output_path() {
234+
let config = DatabaseConfig {
235+
driver_name: "test_driver".to_string(),
236+
uri: None,
237+
username: None,
238+
password: None,
239+
options: vec![],
240+
query_source: QuerySource::Query("SELECT 1".to_string()),
241+
table_mode: TableMode::default(),
242+
output_path: Some(PathBuf::from("output.json")),
243+
};
244+
245+
assert_eq!(config.output_path, Some(PathBuf::from("output.json")));
246+
}
217247
}

src/database.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use crate::cli::DatabaseConfig;
5-
use crate::table::{self, TableMode};
65
use adbc_core::options::{AdbcVersion, OptionDatabase, OptionValue};
76
use adbc_core::{Connection, Database, Driver, LOAD_FLAG_DEFAULT, Statement};
87
use adbc_driver_manager::ManagedDriver;
8+
use arrow_array::RecordBatch;
99
use std::process::exit;
1010

1111
pub fn initialize_connection(config: DatabaseConfig) -> impl Connection {
@@ -61,10 +61,9 @@ pub fn initialize_connection(config: DatabaseConfig) -> impl Connection {
6161
pub fn execute_query(
6262
connection: &mut impl adbc_core::Connection,
6363
sql: &str,
64-
table_mode: TableMode,
65-
) -> Result<(), String> {
64+
) -> Result<Vec<RecordBatch>, String> {
6665
if sql.trim().is_empty() {
67-
return Ok(());
66+
return Ok(vec![]);
6867
}
6968

7069
let mut statement = connection
@@ -79,9 +78,9 @@ pub fn execute_query(
7978
.execute()
8079
.map_err(|e| format!("Failed to execute statement: {e}"))?;
8180

82-
let batches: Vec<arrow_array::RecordBatch> = reader
81+
let batches: Vec<RecordBatch> = reader
8382
.collect::<Result<_, _>>()
8483
.map_err(|e| format!("Failed to collect batches: {e}"))?;
8584

86-
table::print_batches(&batches, table_mode).map_err(|e| format!("Failed to print batches: {e}"))
85+
Ok(batches)
8786
}

src/main.rs

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mod cli;
55
mod database;
66
mod highlighter;
7+
mod output;
78
mod repl;
89
mod table;
910

@@ -15,44 +16,54 @@ fn main() {
1516
let config = parse_args();
1617
let query_source = config.query_source.clone();
1718
let table_mode = config.table_mode;
19+
let output_path = config.output_path.clone();
1820

19-
match query_source {
20-
QuerySource::Query(sql) => {
21-
let mut connection = database::initialize_connection(config);
22-
if let Err(e) = database::execute_query(&mut connection, &sql, table_mode) {
23-
eprintln!("{e}");
24-
exit(1);
25-
}
26-
}
27-
QuerySource::File(path) => {
28-
let sql = match std::fs::read_to_string(&path) {
29-
Ok(content) => content,
30-
Err(e) => {
31-
eprintln!("Failed to read file {}: {e}", path.display());
32-
exit(1);
33-
}
34-
};
35-
let mut connection = database::initialize_connection(config);
36-
if let Err(e) = database::execute_query(&mut connection, &sql, table_mode) {
37-
eprintln!("{e}");
38-
exit(1);
39-
}
40-
}
21+
if matches!(query_source, QuerySource::Interactive) {
22+
let connection = database::initialize_connection(config);
23+
repl::run_repl(connection, table_mode);
24+
return;
25+
}
26+
27+
let sql = match query_source {
28+
QuerySource::Query(sql) => sql,
29+
QuerySource::File(path) => std::fs::read_to_string(&path).unwrap_or_else(|e| {
30+
eprintln!("Failed to read file {}: {e}", path.display());
31+
exit(1);
32+
}),
4133
QuerySource::Stdin => {
4234
let mut sql = String::new();
43-
if let Err(e) = std::io::stdin().read_to_string(&mut sql) {
44-
eprintln!("Failed to read from stdin: {e}");
45-
exit(1);
46-
}
47-
let mut connection = database::initialize_connection(config);
48-
if let Err(e) = database::execute_query(&mut connection, &sql, table_mode) {
49-
eprintln!("{e}");
50-
exit(1);
51-
}
52-
}
53-
QuerySource::Interactive => {
54-
let connection = database::initialize_connection(config);
55-
repl::run_repl(connection, table_mode);
35+
std::io::stdin()
36+
.read_to_string(&mut sql)
37+
.unwrap_or_else(|e| {
38+
eprintln!("Failed to read from stdin: {e}");
39+
exit(1);
40+
});
41+
sql
5642
}
43+
QuerySource::Interactive => unreachable!(),
44+
};
45+
46+
let mut connection = database::initialize_connection(config);
47+
let batches = database::execute_query(&mut connection, &sql).unwrap_or_else(|e| {
48+
eprintln!("{e}");
49+
exit(1);
50+
});
51+
if let Err(e) = output_results(&batches, table_mode, output_path.as_deref()) {
52+
eprintln!("{e}");
53+
exit(1);
54+
}
55+
}
56+
57+
fn output_results(
58+
batches: &[arrow_array::RecordBatch],
59+
table_mode: table::TableMode,
60+
output_path: Option<&std::path::Path>,
61+
) -> Result<(), String> {
62+
if let Some(path) = output_path {
63+
output::write_batches_to_file(batches, path)
64+
.map_err(|e| format!("Failed to write output file: {e}"))
65+
} else {
66+
table::print_batches(batches, table_mode)
67+
.map_err(|e| format!("Failed to print batches: {e}"))
5768
}
5869
}

0 commit comments

Comments
 (0)