Skip to content

Commit 0cbcf47

Browse files
authored
Merge pull request #103 from hotdata-dev/feat/query-arrow-ipc
feat(query): fetch results as Arrow IPC instead of JSON
2 parents da60348 + 99e7736 commit 0cbcf47

8 files changed

Lines changed: 724 additions & 58 deletions

File tree

Cargo.lock

Lines changed: 458 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ reqwest = { version = "0.12", features = ["blocking", "json"] }
1919
rayon = "1.10"
2020
serde = { version = "1", features = ["derive"] }
2121
serde_json = "1"
22+
arrow = { version = "54", default-features = false, features = ["ipc"] }
2223
serde_yaml = "0.9"
2324
base64 = "0.22"
2425
crossterm = "0.28"

src/api.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,20 @@ impl ApiClient {
277277
self.send(req, None)
278278
}
279279

280+
/// GET with a custom Accept header; returns raw bytes instead of decoded text.
281+
/// Used for binary result formats such as Arrow IPC streams.
282+
pub fn get_bytes(&self, path: &str, accept: &str) -> (reqwest::StatusCode, Vec<u8>) {
283+
let url = format!("{}{path}", self.api_url);
284+
let req = self.build_request(reqwest::Method::GET, &url).header("Accept", accept);
285+
match util::send_debug_bytes(&self.client, req) {
286+
Ok(pair) => pair,
287+
Err(e) => {
288+
eprintln!("error connecting to API: {e}");
289+
std::process::exit(1);
290+
}
291+
}
292+
}
293+
280294
/// POST request with JSON body, exits on error, returns raw (status, body).
281295
pub fn post_raw(&self, path: &str, body: &serde_json::Value) -> (reqwest::StatusCode, String) {
282296
let url = format!("{}{path}", self.api_url);

src/command.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,16 @@ pub enum DatabasesCommands {
547547
output: String,
548548
},
549549

550+
/// Show details for a specific managed database
551+
Show {
552+
/// Database name or ID
553+
name_or_id: String,
554+
555+
/// Output format
556+
#[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "yaml"])]
557+
output: String,
558+
},
559+
550560
/// Create a new managed database
551561
Create {
552562
/// Optional display label (not unique, not an identifier — databases are addressed by id)
@@ -604,8 +614,11 @@ pub enum DatabasesCommands {
604614

605615
/// Manage tables inside a managed database
606616
Tables {
617+
/// Database id or description — shorthand for `tables list` when no subcommand is given
618+
database: Option<String>,
619+
607620
#[command(subcommand)]
608-
command: DatabaseTablesCommands,
621+
command: Option<DatabaseTablesCommands>,
609622
},
610623
}
611624

@@ -738,6 +751,8 @@ pub enum SkillCommands {
738751
},
739752
/// Show the installation status of the hotdata skill
740753
Status,
754+
/// List installed skills and their versions (alias for status)
755+
List,
741756
}
742757

743758
#[derive(Subcommand)]

src/main.rs

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,9 @@ fn main() {
390390
Some(DatabasesCommands::List { output }) => {
391391
databases::list(&workspace_id, &output)
392392
}
393+
Some(DatabasesCommands::Show { name_or_id, output }) => {
394+
databases::get(&workspace_id, &name_or_id, &output)
395+
}
393396
Some(DatabasesCommands::Create {
394397
description,
395398
schema,
@@ -427,43 +430,63 @@ fn main() {
427430
upload_id.as_deref(),
428431
)
429432
}
430-
Some(DatabasesCommands::Tables { command }) => match command {
431-
DatabaseTablesCommands::List {
432-
database,
433+
Some(DatabasesCommands::Tables { database, command }) => match command {
434+
Some(DatabaseTablesCommands::List {
435+
database: db_flag,
433436
schema,
434437
output,
435-
} => databases::tables_list(
438+
}) => databases::tables_list(
436439
&workspace_id,
437-
database.as_deref(),
440+
db_flag.as_deref().or(database.as_deref()),
438441
schema.as_deref(),
439442
&output,
440443
),
441-
DatabaseTablesCommands::Load {
442-
database,
444+
Some(DatabaseTablesCommands::Load {
445+
database: db_flag,
443446
table,
444447
schema,
445448
file,
446449
url,
447450
upload_id,
448-
} => databases::tables_load(
451+
}) => databases::tables_load(
449452
&workspace_id,
450-
database.as_deref(),
453+
db_flag.as_deref().or(database.as_deref()),
451454
&table,
452455
Some(schema.as_str()),
453456
file.as_deref(),
454457
url.as_deref(),
455458
upload_id.as_deref(),
456459
),
457-
DatabaseTablesCommands::Delete {
458-
database,
460+
Some(DatabaseTablesCommands::Delete {
461+
database: db_flag,
459462
table,
460463
schema,
461-
} => databases::tables_delete(
464+
}) => databases::tables_delete(
462465
&workspace_id,
463-
database.as_deref(),
466+
db_flag.as_deref().or(database.as_deref()),
464467
&table,
465468
Some(schema.as_str()),
466469
),
470+
None => {
471+
if let Some(ref db) = database {
472+
databases::tables_list(
473+
&workspace_id,
474+
Some(db.as_str()),
475+
None,
476+
"table",
477+
)
478+
} else {
479+
use clap::CommandFactory;
480+
let mut cmd = Cli::command();
481+
cmd.build();
482+
cmd.find_subcommand_mut("databases")
483+
.unwrap()
484+
.find_subcommand_mut("tables")
485+
.unwrap()
486+
.print_help()
487+
.unwrap();
488+
}
489+
}
467490
},
468491
None => {
469492
use clap::CommandFactory;
@@ -507,7 +530,7 @@ fn main() {
507530
skill::install()
508531
}
509532
}
510-
SkillCommands::Status => skill::status(),
533+
SkillCommands::Status | SkillCommands::List => skill::status(),
511534
},
512535
Commands::Results {
513536
result_id,

0 commit comments

Comments
 (0)