From 7a53515bd81664b6dfea0158659a978b041cafa1 Mon Sep 17 00:00:00 2001 From: dario curreri Date: Wed, 22 Jul 2026 12:40:00 +0200 Subject: [PATCH] feat: allow configuring datafusion-cli history file location datafusion-cli hardcoded the interactive shell's rustyline history as the relative path `.history`, so every launch directory silently got its own history file with no way to point it elsewhere. Add a `--history-file ` flag and a `DATAFUSION_HISTORY_FILE` environment variable (flag takes precedence), keeping `.history` as the default so existing behavior is unchanged. Closes #23796. --- .../examples/cli-session-context.rs | 4 ++- datafusion-cli/src/exec.rs | 7 ++-- datafusion-cli/src/main.rs | 35 +++++++++++++++++-- docs/source/user-guide/cli/usage.md | 2 ++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/datafusion-cli/examples/cli-session-context.rs b/datafusion-cli/examples/cli-session-context.rs index 6095072163870..fbc82923726e2 100644 --- a/datafusion-cli/examples/cli-session-context.rs +++ b/datafusion-cli/examples/cli-session-context.rs @@ -94,5 +94,7 @@ pub async fn main() { instrumented_registry: Arc::new(InstrumentedObjectStoreRegistry::new()), }; - exec_from_repl(&my_ctx, &mut print_options).await.unwrap(); + exec_from_repl(&my_ctx, &mut print_options, None) + .await + .unwrap(); } diff --git a/datafusion-cli/src/exec.rs b/datafusion-cli/src/exec.rs index bc2c15f48debb..42f7dcb88f044 100644 --- a/datafusion-cli/src/exec.rs +++ b/datafusion-cli/src/exec.rs @@ -48,6 +48,7 @@ use std::collections::HashMap; use std::fs::File; use std::io::BufReader; use std::io::prelude::*; +use std::path::Path; use tokio::signal; /// run and execute SQL statements and commands, against a context with the given print options @@ -129,13 +130,15 @@ pub async fn exec_from_files( pub async fn exec_from_repl( ctx: &dyn CliSessionContext, print_options: &mut PrintOptions, + history_file: Option<&Path>, ) -> rustyline::Result<()> { + let history_file = history_file.unwrap_or_else(|| Path::new(".history")); let mut rl = Editor::new()?; rl.set_helper(Some(CliHelper::new( &ctx.task_ctx().session_config().options().sql_parser.dialect, print_options.color, ))); - rl.load_history(".history").ok(); + rl.load_history(history_file).ok(); loop { match rl.readline("> ") { @@ -210,7 +213,7 @@ pub async fn exec_from_repl( } } - rl.save_history(".history") + rl.save_history(history_file) } pub(super) async fn exec_and_print( diff --git a/datafusion-cli/src/main.rs b/datafusion-cli/src/main.rs index 20a2537d7c10c..00739e2951453 100644 --- a/datafusion-cli/src/main.rs +++ b/datafusion-cli/src/main.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; use std::env; use std::num::NonZeroUsize; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::ExitCode; use std::sync::{Arc, LazyLock}; @@ -109,6 +109,13 @@ struct Args { )] rc: Option>, + #[clap( + long, + help = "Path to the file used to persist interactive shell history. Overrides DATAFUSION_HISTORY_FILE if set, default to .history", + value_parser(parse_valid_history_file) + )] + history_file: Option, + #[clap(long, value_enum, default_value_t = PrintFormat::Automatic)] format: PrintFormat, @@ -304,12 +311,17 @@ async fn main_inner() -> Result<()> { } }; + let history_file = args + .history_file + .map(PathBuf::from) + .or_else(|| env::var_os("DATAFUSION_HISTORY_FILE").map(PathBuf::from)); + if repl_mode { if !rc.is_empty() { exec::exec_from_files(&ctx, rc, &print_options).await?; } // TODO maybe we can have thiserror for cli but for now let's keep it simple - return exec::exec_from_repl(&ctx, &mut print_options) + return exec::exec_from_repl(&ctx, &mut print_options, history_file.as_deref()) .await .map_err(|e| DataFusionError::External(Box::new(e))); } @@ -379,6 +391,25 @@ fn parse_valid_data_dir(dir: &str) -> Result { } } +fn parse_valid_history_file(path: &str) -> Result { + let path = Path::new(path); + if path.is_dir() { + return Err(format!( + "Invalid history file '{}': is a directory", + path.display() + )); + } + match path.parent() { + Some(parent) if !parent.as_os_str().is_empty() && !parent.is_dir() => { + Err(format!( + "Invalid history file '{}': parent directory does not exist", + path.display() + )) + } + _ => Ok(path.to_string_lossy().into_owned()), + } +} + fn parse_batch_size(size: &str) -> Result { match size.parse::() { Ok(size) if size > 0 => Ok(size), diff --git a/docs/source/user-guide/cli/usage.md b/docs/source/user-guide/cli/usage.md index 75c6698f007a5..a4fe62f17f29b 100644 --- a/docs/source/user-guide/cli/usage.md +++ b/docs/source/user-guide/cli/usage.md @@ -39,6 +39,8 @@ Options: Execute commands from file(s), then exit -r, --rc [...] Run the provided files on startup instead of ~/.datafusionrc + --history-file + Path to the file used to persist interactive shell history. Overrides DATAFUSION_HISTORY_FILE if set, default to .history --format [default: automatic] [possible values: csv, tsv, table, json, nd-json, automatic] -q, --quiet