Skip to content

Conversation

@gruuya
Copy link
Contributor

@gruuya gruuya commented Nov 21, 2023

Add a basic CLI implementation that supports searchable history, multi-line comments and multi-statement queries:

gruuya@markogrujics-MacBook-Pro seafowl % cargo run -- --cli                                            
   Compiling seafowl v0.5.2 (/Users/gruuya/Splitgraph/seafowl)
    Finished dev [unoptimized + debuginfo] target(s) in 5.06s
     Running `target/debug/seafowl --cli`
default> create table t as values (1, 'one'), (2, 'two');
Error during planning: Table "t" already exists
default> drop table t;
Time: 0.006s
default> create table t as values (1, 'one'), (2, 'two');
Time: 0.023s
default> select *
from t
where column1 > 1;
+---------+---------+
| column1 | column2 |
+---------+---------+
| 2       | two     |
+---------+---------+
Time: 0.024s
default> select column1 from t; select column2 from t;
+---------+
| column1 |
+---------+
| 1       |
| 2       |
+---------+
+---------+
| column2 |
+---------+
| one     |
| two     |
+---------+
Time: 0.018s

Validation and highlighting can be covered separately.

Closes #467

@gruuya gruuya self-assigned this Nov 21, 2023
@gruuya gruuya requested a review from mildbyte November 21, 2023 14:39
@gruuya gruuya force-pushed the seafowl-cli-impl branch 2 times, most recently from 596e312 to 7216c96 Compare November 22, 2023 14:15
README.md Outdated
## CLI

Seafowl also provides a CLI to accommodate frictionless prototyping, troubleshooting and testing of
the core features :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
the core features :
the core features:

README.md Outdated

It does so by circumventing Seafowl's primary HTTP interface, which involves properly formatting
HTTP requests with queries, authentication, as well as dealing with potentially faulty networking
setup, which can sometimes be too tedious for a quick manual interactive session.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setup, which can sometimes be too tedious for a quick manual interactive session.
setups, which can sometimes be too tedious for a quick manual interactive session.

Comment on lines 16 to 23
#[allow(clippy::if_same_then_else)]
fn validate_input(&self, input: &str) -> Result<ValidationResult> {
if input.ends_with(';') {
// TODO: actually perform validation here
Ok(ValidationResult::Valid(None))
} else if input.starts_with('\\') {
// command
Ok(ValidationResult::Valid(None))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: could fold into one clause and avoid the clippy pragma

Suggested change
#[allow(clippy::if_same_then_else)]
fn validate_input(&self, input: &str) -> Result<ValidationResult> {
if input.ends_with(';') {
// TODO: actually perform validation here
Ok(ValidationResult::Valid(None))
} else if input.starts_with('\\') {
// command
Ok(ValidationResult::Valid(None))
fn validate_input(&self, input: &str) -> Result<ValidationResult> {
if input.ends_with(';') || input.starts_with('\\') { // TODO: actually perform validation here for ;
Ok(ValidationResult::Valid(None))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left it broken up intentionally as we'd need validation only in the first step, though come to think of it that's probably not necessary as validation is also performed once input is entered (i.e. during parsing/planning).

src/cli/mod.rs Outdated
pub async fn repl_loop(&self) -> rustyline::Result<()> {
let mut rl = Editor::new()?;
rl.set_helper(Some(CliHelper {}));
rl.load_history(".history").ok();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: should be a constant

src/cli/mod.rs Outdated
}
}

rl.save_history(".history")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here with the constant

src/cli/mod.rs Outdated
}

// Interactive loop for running commands from a CLI
pub async fn repl_loop(&self) -> rustyline::Result<()> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: repl is already a read-evaluate-print-loop, so repl_loop is redundant 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, renaming 😄


const TEST_CONFIG_FILE: &str = "seafowl-test.toml";

fn setup_temp_config_and_data_dir() -> std::io::Result<TempDir> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we don't have any of this as an existing fixture in integration tests? I guess we always inject a pre-created context instead of setting up a config file for Seafowl.

Is there a specific reason we have to use on-disk state here instead of in-memory? Doesn't seem like the CLI tests care about persistence when restarting the CLI anyway.

Copy link
Contributor Author

@gruuya gruuya Nov 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we don't have any of this as an existing fixture in integration tests? I guess we always inject a pre-created context instead of setting up a config file for Seafowl.

That is correct, we didn't have this before. Typically we use

/// Make a SeafowlContext that's connected to a real PostgreSQL database
async fn make_context_with_pg(
object_store_type: ObjectStoreType,
) -> (DefaultSeafowlContext, Option<TempDir>) {

to get a Seafowl context object directly.

Is there a specific reason we have to use on-disk state here instead of in-memory? Doesn't seem like the CLI tests care about persistence when restarting the CLI anyway.

In principle there's no difference for this particular test whether we use local FS or in-mem storage. However, given that the test starts up the Seafowl bin as an external command there's no way to supply the config programmatically to it directly, short of saving a config file on disk and then supplying the config file via the -c arg

    let temp_dir = setup_temp_config_and_data_dir()?;
    let mut cmd = Command::cargo_bin("seafowl").expect("Seafowl bin exists");
    cmd.arg("--cli")
        .arg("-c")
        .arg(temp_dir.path().join(TEST_CONFIG_FILE))
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());

Since we need the temp dir for storing the config anyway I just went with the local FS storage in there as well.

I guess an alternative is to extend make_context_with_pg such that it always generates a temp dir (not only in the case of ObjectStoreType::Local as now), and then also persist the generated config file there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess an alternative is to extend make_context_with_pg such that it always generates a temp dir (not only in the case of ObjectStoreType::Local as now), and then also persist the generated config file there.

Ok, I'll go ahead with the merge, and then potentially address this in the follow-up PRs.

@gruuya gruuya merged commit 5a76ce9 into main Nov 28, 2023
@gruuya gruuya deleted the seafowl-cli-impl branch November 28, 2023 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a Seafowl CLI

3 participants