-
Notifications
You must be signed in to change notification settings - Fork 19
Seafowl CLI initial implementation #468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
73e5790 to
b3f5258
Compare
596e312 to
7216c96
Compare
7216c96 to
e76e4c1
Compare
README.md
Outdated
| ## CLI | ||
|
|
||
| Seafowl also provides a CLI to accommodate frictionless prototyping, troubleshooting and testing of | ||
| the core features : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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. |
src/cli/helper.rs
Outdated
| #[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)) |
There was a problem hiding this comment.
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
| #[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)) |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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<()> { |
There was a problem hiding this comment.
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 😅
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
seafowl/tests/statements/mod.rs
Lines 50 to 53 in 3d428c9
| /// 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.
There was a problem hiding this comment.
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.
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.018sValidation and highlighting can be covered separately.
Closes #467