feat: support explicit read-write database connections

This commit is contained in:
Valdemar Lorenzen
2026-10-20 13:57:51 +02:00
parent 2aac7361d6
commit 4f472b55f3
6 changed files with 617 additions and 98 deletions
+43 -9
View File
@@ -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(())
}
}