Skip to content

Commit 03e1b13

Browse files
Add SQL output format option (#5459)
# Description of Changes Adds `--format` to `spacetime sql`, matching `spacetime logs` with `text` as the default and `json` as the raw API response output. The REPL path passes the selected format through to each query while preserving the existing text output and stats behavior by default. Closes #5450. # API and ABI breaking changes None. # Expected complexity level and risk 1. Small CLI-only output option. Default behavior is unchanged. # Testing - [x] `PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH /Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/rustfmt --check crates/cli/src/subcommands/sql.rs crates/cli/src/subcommands/repl.rs` - [x] `git diff --check` - [x] `PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH /Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/cargo check -p spacetimedb-cli` - [x] `PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH /Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/cargo test -p spacetimedb-cli subcommands::sql::tests:: -- --nocapture` - [x] `PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH /Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/cargo run -p spacetimedb-cli -- sql --help` - [x] `PATH=/Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin:$PATH /Users/clockworklabs/.rustup/toolchains/1.93.0-aarch64-apple-darwin/bin/cargo ci self-docs --check` Note: `cargo ci self-docs --check` printed the existing warning `argument dotnet is missing help text`. Note: `cargo fmt --all --check` could not be run in this shell because the installed cargo toolchains do not expose `cargo-fmt`; `rustfmt --check` was run directly on the touched Rust files instead. --------- Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
1 parent cbccc96 commit 03e1b13

3 files changed

Lines changed: 50 additions & 6 deletions

File tree

crates/cli/src/subcommands/repl.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::api::{ClientApi, Connection};
2-
use crate::sql::run_sql;
2+
use crate::sql::{run_sql, Format};
33
use colored::*;
44
use dirs::home_dir;
55
use std::env::temp_dir;
@@ -38,7 +38,7 @@ sort by
3838
.clear
3939
";
4040

41-
pub async fn exec(con: Connection) -> Result<(), anyhow::Error> {
41+
pub(crate) async fn exec(con: Connection, format: Format) -> Result<(), anyhow::Error> {
4242
let database = con.database.clone();
4343
let mut rl = Editor::<ReplHelper, DefaultHistory>::new().unwrap();
4444
let history = home_dir().unwrap_or_else(temp_dir).join(".stdb.history.txt");
@@ -71,7 +71,7 @@ pub async fn exec(con: Connection) -> Result<(), anyhow::Error> {
7171
sql => {
7272
rl.add_history_entry(sql).ok();
7373

74-
if let Err(err) = run_sql(api.sql(), sql, true).await {
74+
if let Err(err) = run_sql(api.sql(), sql, true, format).await {
7575
eprintln!("{}", err.to_string().red())
7676
}
7777
}

crates/cli/src/subcommands/sql.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ pub fn cli() -> clap::Command {
3333
.arg(common_args::confirmed())
3434
.arg(common_args::anonymous())
3535
.arg(common_args::server().help("The nickname, host name or URL of the server hosting the database"))
36+
.arg(
37+
Arg::new("format")
38+
.long("format")
39+
.default_value("text")
40+
.required(false)
41+
.value_parser(clap::value_parser!(Format))
42+
.help("Output format for the SQL results"),
43+
)
3644
.arg(common_args::yes())
3745
.arg(
3846
Arg::new("no_config")
@@ -42,6 +50,25 @@ pub fn cli() -> clap::Command {
4250
)
4351
}
4452

53+
#[derive(Clone, Copy, PartialEq)]
54+
pub(crate) enum Format {
55+
Text,
56+
Json,
57+
}
58+
59+
impl clap::ValueEnum for Format {
60+
fn value_variants<'a>() -> &'a [Self] {
61+
&[Self::Text, Self::Json]
62+
}
63+
64+
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
65+
match self {
66+
Self::Text => Some(clap::builder::PossibleValue::new("text").aliases(["default", "txt"])),
67+
Self::Json => Some(clap::builder::PossibleValue::new("json")),
68+
}
69+
}
70+
}
71+
4572
pub(crate) async fn parse_req(
4673
mut config: Config,
4774
args: &ArgMatches,
@@ -143,7 +170,12 @@ fn print_stmt_result(
143170
Ok(())
144171
}
145172

146-
pub(crate) async fn run_sql(builder: RequestBuilder, sql: &str, with_stats: bool) -> Result<(), anyhow::Error> {
173+
pub(crate) async fn run_sql(
174+
builder: RequestBuilder,
175+
sql: &str,
176+
with_stats: bool,
177+
format: Format,
178+
) -> Result<(), anyhow::Error> {
147179
let now = Instant::now();
148180

149181
let json = builder
@@ -155,6 +187,11 @@ pub(crate) async fn run_sql(builder: RequestBuilder, sql: &str, with_stats: bool
155187
.text()
156188
.await?;
157189

190+
if format == Format::Json {
191+
println!("{json}");
192+
return Ok(());
193+
}
194+
158195
let stmt_result_json: Vec<SqlStmtResult> = serde_json::from_str(&json).context("malformed sql response")?;
159196

160197
let mut out = String::new();
@@ -182,6 +219,7 @@ pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), anyhow::Error
182219
eprintln!("{UNSTABLE_WARNING}\n");
183220
let interactive = args.get_one::<bool>("interactive").unwrap_or(&false);
184221
let no_config = args.get_flag("no_config");
222+
let format = *args.get_one::<Format>("format").unwrap();
185223
let raw_parts: Vec<String> = args
186224
.get_many::<String>("sql_parts")
187225
.map(|vals| vals.cloned().collect())
@@ -201,7 +239,7 @@ pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), anyhow::Error
201239
)?;
202240
let con = parse_req(config, args, &resolved.database, resolved.server.as_deref()).await?;
203241

204-
crate::repl::exec(con).await?;
242+
crate::repl::exec(con, format).await?;
205243
} else {
206244
let resolved = resolve_optional_database_parts(
207245
&raw_parts,
@@ -243,7 +281,7 @@ pub async fn exec(config: Config, args: &ArgMatches) -> Result<(), anyhow::Error
243281
api = api.query(&[("confirmed", if confirmed { "true" } else { "false" })]);
244282
}
245283

246-
run_sql(api, &query, false).await?;
284+
run_sql(api, &query, false, format).await?;
247285
}
248286
Ok(())
249287
}

docs/docs/00300-resources/00200-reference/00100-cli-reference/00100-cli-reference.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ Runs a SQL query on the database. WARNING: This command is UNSTABLE and subject
292292

293293
* `--anonymous` — Perform this action with an anonymous identity
294294
* `-s`, `--server <SERVER>` — The nickname, host name or URL of the server hosting the database
295+
* `--format <FORMAT>` — Output format for the SQL results
296+
297+
Default value: `text`
298+
299+
Possible values: `text`, `json`
300+
295301
* `-y`, `--yes` — Run non-interactively wherever possible. This will answer "yes" to almost all prompts, but will sometimes answer "no" to preserve non-interactivity (e.g. when prompting whether to log in with spacetimedb.com).
296302
* `--no-config` — Ignore spacetime.json configuration
297303

0 commit comments

Comments
 (0)