Skip to content

Commit d2bf005

Browse files
committed
feat(databases): add current database context with set and auto-set on create
1 parent 080d894 commit d2bf005

5 files changed

Lines changed: 107 additions & 17 deletions

File tree

src/command.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,12 @@ pub enum DatabasesCommands {
574574
output: String,
575575
},
576576

577+
/// Set the current database (used by default when no database is specified)
578+
Set {
579+
/// Database id or description
580+
id_or_description: String,
581+
},
582+
577583
/// Delete a managed database and its tables
578584
Delete {
579585
/// Database name or connection ID
@@ -610,8 +616,9 @@ pub enum DatabasesCommands {
610616
pub enum DatabaseTablesCommands {
611617
/// List tables in a managed database
612618
List {
613-
/// Database name or connection ID
614-
database: String,
619+
/// Database id or description (defaults to current database)
620+
#[arg(long)]
621+
database: Option<String>,
615622

616623
/// Filter by schema name
617624
#[arg(long)]
@@ -624,8 +631,9 @@ pub enum DatabaseTablesCommands {
624631

625632
/// Load a parquet file into a table (creates or replaces the table)
626633
Load {
627-
/// Database name or connection ID
628-
database: String,
634+
/// Database id or description (defaults to current database)
635+
#[arg(long)]
636+
database: Option<String>,
629637

630638
/// Table name
631639
table: String,
@@ -649,8 +657,9 @@ pub enum DatabaseTablesCommands {
649657

650658
/// Delete a table from a managed database
651659
Delete {
652-
/// Database name or connection ID
653-
database: String,
660+
/// Database id or description (defaults to current database)
661+
#[arg(long)]
662+
database: Option<String>,
654663

655664
/// Table name
656665
table: String,

src/config.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ pub struct ProfileConfig {
101101
pub workspaces: Vec<WorkspaceEntry>,
102102
#[serde(default, skip_serializing_if = "Option::is_none", alias = "session")]
103103
pub sandbox: Option<String>,
104+
#[serde(default, skip_serializing_if = "Option::is_none")]
105+
pub current_database: Option<String>,
104106
}
105107

106108
#[derive(Debug, Deserialize, Serialize)]
@@ -227,6 +229,38 @@ pub fn clear_sandbox(profile: &str) -> Result<(), String> {
227229
write_config(&config_path, &content)
228230
}
229231

232+
pub fn save_current_database(profile: &str, database_id: &str) -> Result<(), String> {
233+
let config_path = config_path()?;
234+
235+
let mut config_file: ConfigFile = if config_path.exists() {
236+
let content = fs::read_to_string(&config_path)
237+
.map_err(|e| format!("error reading config file: {e}"))?;
238+
serde_yaml::from_str(&content).map_err(|e| format!("error parsing config file: {e}"))?
239+
} else {
240+
ConfigFile { profiles: HashMap::new() }
241+
};
242+
243+
config_file
244+
.profiles
245+
.entry(profile.to_string())
246+
.or_default()
247+
.current_database = Some(database_id.to_string());
248+
249+
let content = serde_yaml::to_string(&config_file)
250+
.map_err(|e| format!("error serializing config: {e}"))?;
251+
write_config(&config_path, &content)
252+
}
253+
254+
pub fn load_current_database(profile: &str) -> Option<String> {
255+
let config_path = config_path().ok()?;
256+
if !config_path.exists() {
257+
return None;
258+
}
259+
let content = fs::read_to_string(&config_path).ok()?;
260+
let config_file: ConfigFile = serde_yaml::from_str(&content).ok()?;
261+
config_file.profiles.get(profile)?.current_database.clone()
262+
}
263+
230264
pub fn resolve_workspace_id(provided: Option<String>, profile_config: &ProfileConfig) -> Result<String, String> {
231265
if let Some(id) = provided {
232266
return Ok(id);

src/databases.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,8 @@ pub fn create(
436436
}
437437
};
438438

439+
let _ = crate::config::save_current_database("default", &result.id);
440+
439441
match format {
440442
"json" => println!("{}", serde_json::to_string_pretty(&result).unwrap()),
441443
"yaml" => print!("{}", serde_yaml::to_string(&result).unwrap()),
@@ -450,6 +452,34 @@ pub fn create(
450452
}
451453
}
452454

455+
pub fn set(id_or_description: &str, workspace_id: &str) {
456+
use crossterm::style::Stylize;
457+
let api = ApiClient::new(Some(workspace_id));
458+
let db = resolve_database(&api, id_or_description);
459+
if let Err(e) = crate::config::save_current_database("default", &db.id) {
460+
eprintln!("{}", format!("error saving current database: {e}").red());
461+
std::process::exit(1);
462+
}
463+
println!("{}", format!("Current database set to {}", db.id).green());
464+
}
465+
466+
fn resolve_current_database(provided: Option<&str>, _workspace_id: &str) -> String {
467+
if let Some(id) = provided {
468+
return id.to_string();
469+
}
470+
match crate::config::load_current_database("default") {
471+
Some(id) => id,
472+
None => {
473+
use crossterm::style::Stylize;
474+
eprintln!(
475+
"{}",
476+
"error: no current database set. Use 'hotdata databases set <id>' or pass a database id.".red()
477+
);
478+
std::process::exit(1);
479+
}
480+
}
481+
}
482+
453483
pub fn delete(workspace_id: &str, id_or_description: &str) {
454484
use crossterm::style::Stylize;
455485

@@ -465,9 +495,10 @@ pub fn delete(workspace_id: &str, id_or_description: &str) {
465495
println!("{}", "Database deleted.".green());
466496
}
467497

468-
pub fn tables_list(workspace_id: &str, database: &str, schema: Option<&str>, format: &str) {
498+
pub fn tables_list(workspace_id: &str, database: Option<&str>, schema: Option<&str>, format: &str) {
499+
let database = resolve_current_database(database, workspace_id);
469500
let api = ApiClient::new(Some(workspace_id));
470-
let db = resolve_database(&api, database);
501+
let db = resolve_database(&api, &database);
471502
let tables = collect_tables(&api, &db.default_connection_id, schema);
472503

473504
let rows = table_rows(tables);
@@ -502,7 +533,7 @@ pub fn tables_list(workspace_id: &str, database: &str, schema: Option<&str>, for
502533

503534
pub fn tables_load(
504535
workspace_id: &str,
505-
database: &str,
536+
database: Option<&str>,
506537
table: &str,
507538
schema: Option<&str>,
508539
file: Option<&str>,
@@ -511,8 +542,9 @@ pub fn tables_load(
511542
) {
512543
use crossterm::style::Stylize;
513544

545+
let database = resolve_current_database(database, workspace_id);
514546
let api = ApiClient::new(Some(workspace_id));
515-
let db = resolve_database(&api, database);
547+
let db = resolve_database(&api, &database);
516548
let schema = schema_name(schema);
517549

518550
// clap enforces mutual exclusion; only one of these is ever Some.
@@ -564,11 +596,12 @@ pub fn tables_load(
564596
println!("rows: {}", result.row_count);
565597
}
566598

567-
pub fn tables_delete(workspace_id: &str, database: &str, table: &str, schema: Option<&str>) {
599+
pub fn tables_delete(workspace_id: &str, database: Option<&str>, table: &str, schema: Option<&str>) {
568600
use crossterm::style::Stylize;
569601

602+
let database = resolve_current_database(database, workspace_id);
570603
let api = ApiClient::new(Some(workspace_id));
571-
let db = resolve_database(&api, database);
604+
let db = resolve_database(&api, &database);
572605
let schema = schema_name(schema);
573606

574607
let path = managed_table_delete_path(&db.default_connection_id, schema, table);

src/main.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,23 @@ extern "C" fn print_sandbox_footer() {
125125
);
126126
}
127127

128+
extern "C" fn print_database_footer() {
129+
use crossterm::style::Stylize;
130+
if let Some(id) = config::load_current_database("default") {
131+
eprintln!(
132+
"{}",
133+
format!("current database: {id} use 'hotdata databases set' to change")
134+
.dark_grey(),
135+
);
136+
}
137+
}
138+
128139
fn main() {
129140
// Register before `Cli::parse`, since `--help` / `--version` exit
130141
// from inside the parser. Safety: `atexit` is async-signal-safe;
131142
// the callback only reads env vars / files and writes to stderr.
132143
unsafe { atexit(print_sandbox_footer) };
144+
unsafe { atexit(print_database_footer) };
133145

134146
dotenvy::dotenv().ok();
135147
let cli = Cli::parse();
@@ -393,6 +405,9 @@ fn main() {
393405
&tables,
394406
&output,
395407
),
408+
Some(DatabasesCommands::Set { id_or_description }) => {
409+
databases::set(&id_or_description, &workspace_id)
410+
}
396411
Some(DatabasesCommands::Delete { name_or_id }) => {
397412
databases::delete(&workspace_id, &name_or_id)
398413
}
@@ -405,7 +420,7 @@ fn main() {
405420
let (database, schema, table) = parse_db_target(&target);
406421
databases::tables_load(
407422
&workspace_id,
408-
&database,
423+
Some(database.as_str()),
409424
&table,
410425
Some(schema.as_str()),
411426
file.as_deref(),
@@ -420,7 +435,7 @@ fn main() {
420435
output,
421436
} => databases::tables_list(
422437
&workspace_id,
423-
&database,
438+
database.as_deref(),
424439
schema.as_deref(),
425440
&output,
426441
),
@@ -433,7 +448,7 @@ fn main() {
433448
upload_id,
434449
} => databases::tables_load(
435450
&workspace_id,
436-
&database,
451+
database.as_deref(),
437452
&table,
438453
Some(schema.as_str()),
439454
file.as_deref(),
@@ -446,7 +461,7 @@ fn main() {
446461
schema,
447462
} => databases::tables_delete(
448463
&workspace_id,
449-
&database,
464+
database.as_deref(),
450465
&table,
451466
Some(schema.as_str()),
452467
),

tests/databases_cli.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ fn databases_tables_load_rejects_both_file_and_upload_id_at_parse_time() {
5252
"databases",
5353
"tables",
5454
"load",
55-
"mydb",
5655
"t1",
5756
"--file",
5857
"a.parquet",

0 commit comments

Comments
 (0)