Skip to content

Commit 2ec6f54

Browse files
committed
feat(databases): resolve database by catalog alias; auto-declare table on load
- try_resolve_database now falls through to match by default_catalog when neither id nor name match, so --catalog works as a lookup key everywhere - databases load auto-recovers from "not declared": deletes the empty database, recreates it with the table declared, then retries the load - Add default_catalog to DatabaseSummary so the list response can be matched without a per-row fetch
1 parent 04f1c70 commit 2ec6f54

1 file changed

Lines changed: 63 additions & 17 deletions

File tree

src/databases.rs

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ struct DatabaseSummary {
1111
id: String,
1212
#[serde(default)]
1313
name: Option<String>,
14+
#[serde(default)]
15+
default_catalog: Option<String>,
1416
}
1517

1618
#[derive(Deserialize)]
@@ -109,21 +111,37 @@ pub fn try_resolve_database(api: &ApiClient, id_or_name: &str) -> Result<Databas
109111
return Ok(db);
110112
}
111113

112-
// Fall back to listing and matching by name.
114+
// Fall back to listing and matching by name or catalog alias.
113115
let body: ListDatabasesResponse = api.get("/databases");
114116
let name_matches: Vec<&DatabaseSummary> = body
115117
.databases
116118
.iter()
117119
.filter(|d| d.name.as_deref() == Some(id_or_name))
118120
.collect();
119121

120-
match name_matches.len() {
122+
if !name_matches.is_empty() {
123+
return match name_matches.len() {
124+
1 => Ok(fetch_database(api, &name_matches[0].id)),
125+
_ => Err(format!(
126+
"multiple databases have name '{}' — use the database id instead",
127+
id_or_name
128+
)),
129+
};
130+
}
131+
132+
let catalog_matches: Vec<&DatabaseSummary> = body
133+
.databases
134+
.iter()
135+
.filter(|d| d.default_catalog.as_deref() == Some(id_or_name))
136+
.collect();
137+
138+
match catalog_matches.len() {
121139
0 => Err(format!(
122-
"no database with id or name '{id_or_name}'"
140+
"no database with id, name, or catalog '{id_or_name}'"
123141
)),
124-
1 => Ok(fetch_database(api, &name_matches[0].id)),
142+
1 => Ok(fetch_database(api, &catalog_matches[0].id)),
125143
_ => Err(format!(
126-
"multiple databases have name '{}' — use the database id instead",
144+
"multiple databases have catalog '{}' — use the database id instead",
127145
id_or_name
128146
)),
129147
}
@@ -781,19 +799,47 @@ pub fn tables_load(
781799
let (status, resp_body) = api.post_raw(&path, &body);
782800
spinner.finish_and_clear();
783801

784-
if !status.is_success() {
785-
let msg = crate::util::api_error(resp_body);
786-
if msg.contains("not declared") {
787-
eprintln!("{}", msg.red());
788-
eprintln!(
789-
"{}",
790-
"Declare the table when creating the database, e.g.:\n \
791-
hotdata databases create --table <table>"
792-
.dark_grey()
793-
);
794-
} else {
795-
eprintln!("{}", msg.red());
802+
let (status, resp_body) = if !status.is_success()
803+
&& crate::util::api_error(resp_body.clone()).contains("not declared")
804+
{
805+
// The table wasn't declared at create time. Delete the database and
806+
// recreate it with the table declared, then retry the load.
807+
let (del_status, del_body) = api.delete_raw(&format!("/databases/{}", db.id));
808+
if !del_status.is_success() {
809+
eprintln!("{}", crate::util::api_error(del_body).red());
810+
std::process::exit(1);
811+
}
812+
let create_body = create_database_request(
813+
db.name.as_deref(),
814+
db.default_catalog.as_deref(),
815+
schema,
816+
&[table.to_string()],
817+
None,
818+
);
819+
let (create_status, create_body_resp) = api.post_raw("/databases", &create_body);
820+
if !create_status.is_success() {
821+
eprintln!("{}", crate::util::api_error(create_body_resp).red());
822+
std::process::exit(1);
796823
}
824+
let new_db: CreateDatabaseResponse = match serde_json::from_str(&create_body_resp) {
825+
Ok(v) => v,
826+
Err(e) => {
827+
eprintln!("error parsing create response: {e}");
828+
std::process::exit(1);
829+
}
830+
};
831+
let _ = crate::config::save_current_database("default", workspace_id, &new_db.id);
832+
let new_path = managed_table_load_path(&new_db.default_connection_id, schema, table);
833+
let spinner = crate::util::spinner("Loading table...");
834+
let result = api.post_raw(&new_path, &body);
835+
spinner.finish_and_clear();
836+
result
837+
} else {
838+
(status, resp_body)
839+
};
840+
841+
if !status.is_success() {
842+
eprintln!("{}", crate::util::api_error(resp_body).red());
797843
std::process::exit(1);
798844
}
799845

0 commit comments

Comments
 (0)