Skip to content

Commit

Permalink
Implement a tabular output format and make it the default for SQL (#1460
Browse files Browse the repository at this point in the history
)

It uses the Printer/Formatter mechanism, since that is what actually
has fully implemented formatters.

I would like to take advantage of the coloring that Printer/Formatter
does, but the table library we use breaks if there are control
characters. I have a plan I might do to fork the library and get
something working, but that would be a follow-up.

This adds a new tabular output-format that works for both edgeql
and SQL. We then add a sql-output-format mode that defaults to
tabular, but it can still be set back to default.

Fixes #1451.
  • Loading branch information
msullivan authored Feb 1, 2025
1 parent 58696b0 commit cd04abf
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/commands/backslash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ pub fn get_setting(s: &Setting, prompt: &repl::State) -> Cow<'static, str> {
Language(_) => prompt.input_language.as_str().into(),
HistorySize(_) => prompt.history_limit.to_string().into(),
OutputFormat(_) => prompt.output_format.as_str().into(),
SqlOutputFormat(_) => prompt.sql_output_format.as_str().into(),
DisplayTypenames(_) => bool_str(prompt.display_typenames).into(),
ExpandStrings(_) => bool_str(prompt.print.expand_strings).into(),
PrintStats(_) => prompt.print_stats.as_str().into(),
Expand Down Expand Up @@ -650,6 +651,9 @@ pub async fn execute(
OutputFormat(c) => {
prompt.output_format = c.value.expect("only writes here");
}
SqlOutputFormat(c) => {
prompt.sql_output_format = c.value.expect("only writes here");
}
DisplayTypenames(b) => {
prompt.display_typenames = b.unwrap_value();
}
Expand Down
2 changes: 2 additions & 0 deletions src/commands/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ pub enum Setting {
VectorDisplayLength(VectorLimitValue),
/// Set output format
OutputFormat(OutputFormat),
/// Set SQL output format
SqlOutputFormat(OutputFormat),
/// Display typenames in default output mode
DisplayTypenames(SettingBool),
/// Disable escaping newlines in quoted strings
Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub struct ShellConfig {
pub input_language: Option<repl::InputLanguage>,
#[serde(with = "serde_str::opt", default)]
pub output_format: Option<repl::OutputFormat>,
#[serde(with = "serde_str::opt", default)]
pub sql_output_format: Option<repl::OutputFormat>,
#[serde(default)]
pub display_typenames: Option<bool>,
#[serde(with = "serde_str::opt", default)]
Expand Down
32 changes: 30 additions & 2 deletions src/interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ pub fn main(options: Options, cfg: Config) -> Result<(), anyhow::Error> {
.output_format
.or(cfg.shell.output_format)
.unwrap_or(repl::OutputFormat::Default),
sql_output_format: options
.sql_output_format
.or(cfg.shell.sql_output_format)
.unwrap_or(repl::OutputFormat::Tabular),
display_typenames: cfg.shell.display_typenames.unwrap_or(true),
input_mode: cfg.shell.input_mode.unwrap_or(repl::InputMode::Emacs),
print_stats: cfg.shell.print_stats.unwrap_or(repl::PrintStats::Off),
Expand Down Expand Up @@ -277,14 +281,20 @@ async fn execute_query(
use crate::repl::PrintStats::*;

let cli = state.connection.as_mut().expect("connection established");

let output_format = match state.input_language {
repl::InputLanguage::EdgeQl => state.output_format,
repl::InputLanguage::Sql => state.sql_output_format,
};

let flags = CompilationOptions {
implicit_limit: state.implicit_limit.map(|x| (x + 1) as u64),
implicit_typenames: state.display_typenames && cli.protocol().supports_inline_typenames(),
implicit_typeids: false,
explicit_objectids: true,
allow_capabilities: Capabilities::ALL,
input_language: state.input_language.into(),
io_format: state.output_format.into(),
io_format: output_format.into(),
expected_cardinality: Cardinality::Many,
};

Expand Down Expand Up @@ -373,7 +383,7 @@ async fn execute_query(
// update max_width each time
cfg.max_width(w.into());
}
match state.output_format {
match output_format {
TabSeparated => {
let mut index = 0;
while let Some(row) = items.next().await.transpose()? {
Expand Down Expand Up @@ -408,6 +418,24 @@ async fn execute_query(
index += 1;
}
}
Tabular => {
match print::table_to_stdout(&mut items, &cfg).await {
Ok(()) => {}
Err(e) => {
match e {
PrintError::StreamErr {
source: ref error, ..
} => {
print_query_error(error, statement, state.verbose_errors, "<query>")?;
}
_ => eprintln!("{e:#?}"),
}
state.last_error = Some(e.into());
return Err(QueryError)?;
}
}
return Err(QueryError)?;
}
Default => {
match print::native_to_stdout(&mut items, &cfg).await {
Ok(()) => {}
Expand Down
36 changes: 28 additions & 8 deletions src/non_interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ use crate::statement::{read_statement, EndOfFile};

#[tokio::main(flavor = "current_thread")]
pub async fn noninteractive_main(q: &Query, options: &Options) -> Result<(), anyhow::Error> {
let lang = if let Some(l) = q.input_language {
l
} else {
repl::InputLanguage::EdgeQl
};

// There's some extra complexity here due to the fact that we
// have to support now deprecated top-level `--json` and
// `--tab-separated` flags.
Expand All @@ -42,17 +48,15 @@ pub async fn noninteractive_main(q: &Query, options: &Options) -> Result<(), any
fmt
} else {
// Means "native" serialization; for `edgedb query`
// the default is `json-pretty`.
repl::OutputFormat::JsonPretty
// the default is `json-pretty` for edgeql and `tabular` for SQL.
// TODO: Something more machine readable for SQL?
match lang {
repl::InputLanguage::EdgeQl => repl::OutputFormat::JsonPretty,
repl::InputLanguage::Sql => repl::OutputFormat::Tabular,
}
}
};

let lang = if let Some(l) = q.input_language {
l
} else {
repl::InputLanguage::EdgeQl
};

if let Some(filename) = &q.file {
if filename == "-" {
interpret_file(&mut stdin(), options, fmt, lang).await?;
Expand Down Expand Up @@ -191,6 +195,22 @@ async fn _run_query(
stdout().lock().write_all(text.as_bytes())?;
}
}
repl::OutputFormat::Tabular => match print::table_to_stdout(&mut items, &cfg).await {
Ok(()) => {}
Err(e) => {
match e {
PrintError::StreamErr {
source: ref error, ..
} => {
print::error!("{error}");
}
_ => {
print::error!("{e}");
}
}
return Ok(());
}
},
repl::OutputFormat::Default => match print::native_to_stdout(&mut items, &cfg).await {
Ok(()) => {}
Err(e) => {
Expand Down
2 changes: 2 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ pub struct Options {
pub debug_print_codecs: bool,
pub input_language: Option<InputLanguage>,
pub output_format: Option<OutputFormat>,
pub sql_output_format: Option<OutputFormat>,
pub no_cli_update_check: bool,
pub test_output_conn_params: bool,
}
Expand Down Expand Up @@ -853,6 +854,7 @@ impl Options {
} else {
None
},
sql_output_format: None,
no_cli_update_check,
test_output_conn_params: args.test_output_conn_params,
})
Expand Down
4 changes: 2 additions & 2 deletions src/portable/project/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ fn do_init(
let handle = project::Handle {
name: name.into(),
project_dir: project.location.root.clone(),
schema_dir: project.resolve_schema_dir()?.into(),
schema_dir: project.resolve_schema_dir()?,
instance,
database: options.database.clone(),
};
Expand Down Expand Up @@ -453,7 +453,7 @@ fn do_cloud_init(
let handle = project::Handle {
name: full_name.clone(),
project_dir: project.location.root.clone(),
schema_dir: project.resolve_schema_dir()?.into(),
schema_dir: project.resolve_schema_dir()?,
instance: project::InstanceKind::Remote,
database: Some(database.to_owned()),
};
Expand Down
14 changes: 14 additions & 0 deletions src/print/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::print::Printer;

use Delim::*;

use std::convert::Infallible;

const HIGH_WATER_MARK: usize = 4096;

#[derive(Debug)] // no Error trait, this struct should not escape to user
Expand All @@ -26,6 +28,18 @@ pub(in crate::print) enum Delim {
Field,
}

pub trait UnwrapInfallible<T>: Sized {
fn unwrap_infallible(self) -> T;
}
impl<T> UnwrapInfallible<T> for std::result::Result<T, Infallible> {
fn unwrap_infallible(self) -> T {
match self {
Ok(v) => v,
Err(i) => match i {},
}
}
}

pub(in crate::print) type Result<E> = std::result::Result<(), Exception<E>>;

pub trait WrapErr<T, E>: Sized {
Expand Down
Loading

0 comments on commit cd04abf

Please sign in to comment.