feat: support explicit read-write database connections
This commit is contained in:
+43
-9
@@ -73,6 +73,10 @@ impl App {
|
||||
self.db.name()
|
||||
}
|
||||
|
||||
pub(crate) fn access_mode(&self) -> crate::db::AccessMode {
|
||||
self.db.access_mode()
|
||||
}
|
||||
|
||||
pub(crate) fn database_name(&self) -> &str {
|
||||
self.db.display_name()
|
||||
}
|
||||
@@ -200,13 +204,16 @@ impl App {
|
||||
fn execute_query(&mut self) {
|
||||
match self.db.query(&self.query.value) {
|
||||
Ok(result) => {
|
||||
self.error = None;
|
||||
if self.db.access_mode() == crate::db::AccessMode::ReadWrite {
|
||||
self.refresh();
|
||||
}
|
||||
self.rows_state = TableState::default()
|
||||
.with_selected((!result.page.rows.is_empty()).then_some(0))
|
||||
.with_selected_column((!result.headers.is_empty()).then_some(0));
|
||||
self.column_offset = 0;
|
||||
self.show_schema = false;
|
||||
self.query_result = Some(result);
|
||||
self.error = None;
|
||||
}
|
||||
Err(error) => self.error = Some(error_text(error.as_ref())),
|
||||
}
|
||||
@@ -363,7 +370,7 @@ impl App {
|
||||
});
|
||||
let description = if let Some(result) = &self.query_result {
|
||||
format!(
|
||||
" Query · {} rows{} · columns {}–{}/{}",
|
||||
" Query · {} rows{} · columns {}–{}/{}{}",
|
||||
result.page.rows.len(),
|
||||
if result.page.has_next {
|
||||
" (first 100)"
|
||||
@@ -373,6 +380,7 @@ impl App {
|
||||
if count > 0 { self.column_offset + 1 } else { 0 },
|
||||
self.column_offset + visible,
|
||||
count,
|
||||
result.affected_rows.map_or_else(String::new, |rows| format!(" · {rows} rows affected")),
|
||||
)
|
||||
} else if let Some(index) = self.active {
|
||||
let relation = &self.relations[index];
|
||||
@@ -549,7 +557,7 @@ mod tests {
|
||||
fn vim_query_motions_preserve_sql_and_handle_unicode() -> crate::Result<()> {
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
let key = |character| KeyEvent::new(KeyCode::Char(character), KeyModifiers::NONE);
|
||||
app.handle_query_key(key('i'));
|
||||
app.paste_query(" SELECT café,界\n\n FROM data");
|
||||
@@ -597,7 +605,7 @@ mod tests {
|
||||
fn query_modes_preserve_literal_text_and_navigation() -> crate::Result<()> {
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
let key = |character| KeyEvent::new(KeyCode::Char(character), KeyModifiers::NONE);
|
||||
assert!(!app.query_insert_mode());
|
||||
app.paste_query("ignored");
|
||||
@@ -642,7 +650,7 @@ mod tests {
|
||||
fn execution_shortcuts_work_in_both_query_modes() -> crate::Result<()> {
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
app.handle_query_key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE));
|
||||
app.paste_query("SELECT\n42");
|
||||
for insert in [true, false] {
|
||||
@@ -675,7 +683,7 @@ mod tests {
|
||||
WITH RECURSIVE n(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM n WHERE x<101)
|
||||
INSERT INTO cells SELECT x, x, x, x, x, x FROM n;",
|
||||
)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
let mut terminal = Terminal::new(TestBackend::new(60, 20))?;
|
||||
let key = |character| KeyEvent::new(KeyCode::Char(character), KeyModifiers::NONE);
|
||||
assert_eq!(app.rows_state.selected_cell(), Some((0, 0)));
|
||||
@@ -717,7 +725,7 @@ mod tests {
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use ratatui::{Terminal, backend::TestBackend, style::Color};
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
let mut terminal = Terminal::new(TestBackend::new(60, 14))?;
|
||||
app.handle_query_key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE));
|
||||
app.paste_query("SELECT '界', 42;\n-- comment");
|
||||
@@ -749,7 +757,7 @@ mod tests {
|
||||
rusqlite::Connection::open(file.path())?.execute_batch(
|
||||
"CREATE TABLE comments (id INTEGER); INSERT INTO comments VALUES (9);",
|
||||
)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
app.handle_query_key(KeyEvent::new(KeyCode::Char('i'), KeyModifiers::NONE));
|
||||
app.paste_query("SELECT '界é' AS note,\nNULL AS missing UNION ALL SELECT 'next', 2;");
|
||||
app.handle_query_key(KeyEvent::new(KeyCode::F(5), KeyModifiers::NONE));
|
||||
@@ -783,7 +791,7 @@ mod tests {
|
||||
rusqlite::Connection::open(file.path())?.execute_batch(
|
||||
"CREATE TABLE comments (id INTEGER); INSERT INTO comments VALUES (1);",
|
||||
)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path())?)?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(file.path(), Default::default())?)?;
|
||||
let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(100, 20))?;
|
||||
for (schema, error) in [(false, None), (true, None), (false, Some("Read failed"))] {
|
||||
app.show_schema = schema;
|
||||
@@ -809,4 +817,30 @@ mod tests {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writes_refresh_browsing_and_keep_returning_results() -> crate::Result<()> {
|
||||
let file = tempfile::NamedTempFile::new()?;
|
||||
let mut app = super::App::new(crate::db::Database::open_sqlite(
|
||||
file.path(),
|
||||
crate::db::AccessMode::ReadWrite,
|
||||
)?)?;
|
||||
app.query.value = "CREATE TABLE edited (id INTEGER PRIMARY KEY)".into();
|
||||
app.execute_query();
|
||||
assert!(app.error.is_none(), "{:?}", app.error);
|
||||
assert_eq!(app.relations[0].name, "edited");
|
||||
app.query.value = "INSERT INTO edited VALUES (42) RETURNING id".into();
|
||||
app.execute_query();
|
||||
assert!(app.error.is_none(), "{:?}", app.error);
|
||||
let result = app.query_result.as_ref().unwrap();
|
||||
assert_eq!(result.affected_rows, Some(1));
|
||||
assert_eq!(result.page.rows, [vec![Some("42".into())]]);
|
||||
app.open_relation(Some(0));
|
||||
assert_eq!(app.page.rows, [vec![Some("42".into())]]);
|
||||
app.query.value = "DROP TABLE edited".into();
|
||||
app.execute_query();
|
||||
assert!(app.error.is_none(), "{:?}", app.error);
|
||||
assert!(app.relations.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user