Format problematic DB macros

This commit is contained in:
Julia 2022-12-19 11:11:10 -05:00
parent de9c58d216
commit c49573dc11
2 changed files with 133 additions and 105 deletions

View file

@ -20,8 +20,8 @@ use std::fs::create_dir_all;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use util::{async_iife, ResultExt};
use util::channel::ReleaseChannel; use util::channel::ReleaseChannel;
use util::{async_iife, ResultExt};
const CONNECTION_INITIALIZE_QUERY: &'static str = sql!( const CONNECTION_INITIALIZE_QUERY: &'static str = sql!(
PRAGMA foreign_keys=TRUE; PRAGMA foreign_keys=TRUE;
@ -42,14 +42,17 @@ lazy_static::lazy_static! {
static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
static ref DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(()); static ref DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(());
pub static ref BACKUP_DB_PATH: RwLock<Option<PathBuf>> = RwLock::new(None); pub static ref BACKUP_DB_PATH: RwLock<Option<PathBuf>> = RwLock::new(None);
pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false); pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
} }
/// Open or create a database at the given directory path. /// Open or create a database at the given directory path.
/// This will retry a couple times if there are failures. If opening fails once, the db directory /// This will retry a couple times if there are failures. If opening fails once, the db directory
/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created. /// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created.
/// In either case, static variables are set so that the user can be notified. /// In either case, static variables are set so that the user can be notified.
pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, release_channel: &ReleaseChannel) -> ThreadSafeConnection<M> { pub async fn open_db<M: Migrator + 'static>(
db_dir: &Path,
release_channel: &ReleaseChannel,
) -> ThreadSafeConnection<M> {
if *ZED_STATELESS { if *ZED_STATELESS {
return open_fallback_db().await; return open_fallback_db().await;
} }
@ -69,11 +72,11 @@ pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, release_channel: &Rel
// //
// Basically: Don't ever push invalid migrations to stable or everyone will have // Basically: Don't ever push invalid migrations to stable or everyone will have
// a bad time. // a bad time.
// If no db folder, create one at 0-{channel} // If no db folder, create one at 0-{channel}
create_dir_all(&main_db_dir).context("Could not create db directory")?; create_dir_all(&main_db_dir).context("Could not create db directory")?;
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME)); let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
// Optimistically open databases in parallel // Optimistically open databases in parallel
if !DB_FILE_OPERATIONS.is_locked() { if !DB_FILE_OPERATIONS.is_locked() {
// Try building a connection // Try building a connection
@ -81,7 +84,7 @@ pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, release_channel: &Rel
return Ok(connection) return Ok(connection)
}; };
} }
// Take a lock in the failure case so that we move the db once per process instead // Take a lock in the failure case so that we move the db once per process instead
// of potentially multiple times from different threads. This shouldn't happen in the // of potentially multiple times from different threads. This shouldn't happen in the
// normal path // normal path
@ -89,12 +92,12 @@ pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, release_channel: &Rel
if let Some(connection) = open_main_db(&db_path).await { if let Some(connection) = open_main_db(&db_path).await {
return Ok(connection) return Ok(connection)
}; };
let backup_timestamp = SystemTime::now() let backup_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("System clock is set before the unix timestamp, Zed does not support this region of spacetime") .expect("System clock is set before the unix timestamp, Zed does not support this region of spacetime")
.as_millis(); .as_millis();
// If failed, move 0-{channel} to {current unix timestamp}-{channel} // If failed, move 0-{channel} to {current unix timestamp}-{channel}
let backup_db_dir = db_dir.join(Path::new(&format!( let backup_db_dir = db_dir.join(Path::new(&format!(
"{}-{}", "{}-{}",
@ -110,7 +113,7 @@ pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, release_channel: &Rel
let mut guard = BACKUP_DB_PATH.write(); let mut guard = BACKUP_DB_PATH.write();
*guard = Some(backup_db_dir); *guard = Some(backup_db_dir);
} }
// Create a new 0-{channel} // Create a new 0-{channel}
create_dir_all(&main_db_dir).context("Should be able to create the database directory")?; create_dir_all(&main_db_dir).context("Should be able to create the database directory")?;
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME)); let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
@ -122,10 +125,10 @@ pub async fn open_db<M: Migrator + 'static>(db_dir: &Path, release_channel: &Rel
if let Some(connection) = connection { if let Some(connection) = connection {
return connection; return connection;
} }
// Set another static ref so that we can escalate the notification // Set another static ref so that we can escalate the notification
ALL_FILE_DB_FAILED.store(true, Ordering::Release); ALL_FILE_DB_FAILED.store(true, Ordering::Release);
// If still failed, create an in memory db with a known name // If still failed, create an in memory db with a known name
open_fallback_db().await open_fallback_db().await
} }
@ -179,15 +182,15 @@ macro_rules! define_connection {
&self.0 &self.0
} }
} }
impl $crate::sqlez::domain::Domain for $t { impl $crate::sqlez::domain::Domain for $t {
fn name() -> &'static str { fn name() -> &'static str {
stringify!($t) stringify!($t)
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
$migrations $migrations
} }
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -210,15 +213,15 @@ macro_rules! define_connection {
&self.0 &self.0
} }
} }
impl $crate::sqlez::domain::Domain for $t { impl $crate::sqlez::domain::Domain for $t {
fn name() -> &'static str { fn name() -> &'static str {
stringify!($t) stringify!($t)
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
$migrations $migrations
} }
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -237,134 +240,157 @@ macro_rules! define_connection {
mod tests { mod tests {
use std::{fs, thread}; use std::{fs, thread};
use sqlez::{domain::Domain, connection::Connection}; use sqlez::{connection::Connection, domain::Domain};
use sqlez_macros::sql; use sqlez_macros::sql;
use tempdir::TempDir; use tempdir::TempDir;
use crate::{open_db, DB_FILE_NAME}; use crate::{open_db, DB_FILE_NAME};
// Test bad migration panics // Test bad migration panics
#[gpui::test] #[gpui::test]
#[should_panic] #[should_panic]
async fn test_bad_migration_panics() { async fn test_bad_migration_panics() {
enum BadDB {} enum BadDB {}
impl Domain for BadDB { impl Domain for BadDB {
fn name() -> &'static str { fn name() -> &'static str {
"db_tests" "db_tests"
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test(value);), &[
sql!(CREATE TABLE test(value);),
// failure because test already exists // failure because test already exists
sql!(CREATE TABLE test(value);)] sql!(CREATE TABLE test(value);),
]
} }
} }
let tempdir = TempDir::new("DbTests").unwrap(); let tempdir = TempDir::new("DbTests").unwrap();
let _bad_db = open_db::<BadDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; let _bad_db = open_db::<BadDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
} }
/// Test that DB exists but corrupted (causing recreate) /// Test that DB exists but corrupted (causing recreate)
#[gpui::test] #[gpui::test]
async fn test_db_corruption() { async fn test_db_corruption() {
enum CorruptedDB {} enum CorruptedDB {}
impl Domain for CorruptedDB { impl Domain for CorruptedDB {
fn name() -> &'static str { fn name() -> &'static str {
"db_tests" "db_tests"
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test(value);)] &[sql!(CREATE TABLE test(value);)]
} }
} }
enum GoodDB {} enum GoodDB {}
impl Domain for GoodDB { impl Domain for GoodDB {
fn name() -> &'static str { fn name() -> &'static str {
"db_tests" //Notice same name "db_tests" //Notice same name
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test2(value);)] //But different migration &[sql!(CREATE TABLE test2(value);)] //But different migration
} }
} }
let tempdir = TempDir::new("DbTests").unwrap(); let tempdir = TempDir::new("DbTests").unwrap();
{ {
let corrupt_db = open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; let corrupt_db =
open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
assert!(corrupt_db.persistent()); assert!(corrupt_db.persistent());
} }
let good_db = open_db::<GoodDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; let good_db = open_db::<GoodDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
assert!(good_db.select_row::<usize>("SELECT * FROM test2").unwrap()().unwrap().is_none()); assert!(
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
let mut corrupted_backup_dir = fs::read_dir( .unwrap()
tempdir.path() .is_none()
).unwrap().find(|entry| { );
!entry.as_ref().unwrap().file_name().to_str().unwrap().starts_with("0")
} let mut corrupted_backup_dir = fs::read_dir(tempdir.path())
).unwrap().unwrap().path(); .unwrap()
.find(|entry| {
!entry
.as_ref()
.unwrap()
.file_name()
.to_str()
.unwrap()
.starts_with("0")
})
.unwrap()
.unwrap()
.path();
corrupted_backup_dir.push(DB_FILE_NAME); corrupted_backup_dir.push(DB_FILE_NAME);
dbg!(&corrupted_backup_dir); dbg!(&corrupted_backup_dir);
let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy()); let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy());
assert!(backup.select_row::<usize>("SELECT * FROM test").unwrap()().unwrap().is_none()); assert!(backup.select_row::<usize>("SELECT * FROM test").unwrap()()
.unwrap()
.is_none());
} }
/// Test that DB exists but corrupted (causing recreate) /// Test that DB exists but corrupted (causing recreate)
#[gpui::test] #[gpui::test]
async fn test_simultaneous_db_corruption() { async fn test_simultaneous_db_corruption() {
enum CorruptedDB {} enum CorruptedDB {}
impl Domain for CorruptedDB { impl Domain for CorruptedDB {
fn name() -> &'static str { fn name() -> &'static str {
"db_tests" "db_tests"
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test(value);)] &[sql!(CREATE TABLE test(value);)]
} }
} }
enum GoodDB {} enum GoodDB {}
impl Domain for GoodDB { impl Domain for GoodDB {
fn name() -> &'static str { fn name() -> &'static str {
"db_tests" //Notice same name "db_tests" //Notice same name
} }
fn migrations() -> &'static [&'static str] { fn migrations() -> &'static [&'static str] {
&[sql!(CREATE TABLE test2(value);)] //But different migration &[sql!(CREATE TABLE test2(value);)] //But different migration
} }
} }
let tempdir = TempDir::new("DbTests").unwrap(); let tempdir = TempDir::new("DbTests").unwrap();
{ {
// Setup the bad database // Setup the bad database
let corrupt_db = open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; let corrupt_db =
open_db::<CorruptedDB>(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
assert!(corrupt_db.persistent()); assert!(corrupt_db.persistent());
} }
// Try to connect to it a bunch of times at once // Try to connect to it a bunch of times at once
let mut guards = vec![]; let mut guards = vec![];
for _ in 0..10 { for _ in 0..10 {
let tmp_path = tempdir.path().to_path_buf(); let tmp_path = tempdir.path().to_path_buf();
let guard = thread::spawn(move || { let guard = thread::spawn(move || {
let good_db = smol::block_on(open_db::<GoodDB>(tmp_path.as_path(), &util::channel::ReleaseChannel::Dev)); let good_db = smol::block_on(open_db::<GoodDB>(
assert!(good_db.select_row::<usize>("SELECT * FROM test2").unwrap()().unwrap().is_none()); tmp_path.as_path(),
&util::channel::ReleaseChannel::Dev,
));
assert!(
good_db.select_row::<usize>("SELECT * FROM test2").unwrap()()
.unwrap()
.is_none()
);
}); });
guards.push(guard); guards.push(guard);
} }
for guard in guards.into_iter() { for guard in guards.into_iter() {
assert!(guard.join().is_ok()); assert!(guard.join().is_ok());
} }
} }
} }

View file

@ -8,7 +8,7 @@ use anyhow::{anyhow, bail, Context, Result};
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::Axis; use gpui::Axis;
use util::{ unzip_option, ResultExt}; use util::{unzip_option, ResultExt};
use crate::dock::DockPosition; use crate::dock::DockPosition;
use crate::WorkspaceId; use crate::WorkspaceId;
@ -31,7 +31,7 @@ define_connection! {
timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
) STRICT; ) STRICT;
CREATE TABLE pane_groups( CREATE TABLE pane_groups(
group_id INTEGER PRIMARY KEY, group_id INTEGER PRIMARY KEY,
workspace_id INTEGER NOT NULL, workspace_id INTEGER NOT NULL,
@ -43,7 +43,7 @@ define_connection! {
ON UPDATE CASCADE, ON UPDATE CASCADE,
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
) STRICT; ) STRICT;
CREATE TABLE panes( CREATE TABLE panes(
pane_id INTEGER PRIMARY KEY, pane_id INTEGER PRIMARY KEY,
workspace_id INTEGER NOT NULL, workspace_id INTEGER NOT NULL,
@ -52,7 +52,7 @@ define_connection! {
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE CASCADE ON UPDATE CASCADE
) STRICT; ) STRICT;
CREATE TABLE center_panes( CREATE TABLE center_panes(
pane_id INTEGER PRIMARY KEY, pane_id INTEGER PRIMARY KEY,
parent_group_id INTEGER, // NULL means that this is a root pane parent_group_id INTEGER, // NULL means that this is a root pane
@ -61,7 +61,7 @@ define_connection! {
ON DELETE CASCADE, ON DELETE CASCADE,
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
) STRICT; ) STRICT;
CREATE TABLE items( CREATE TABLE items(
item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique
workspace_id INTEGER NOT NULL, workspace_id INTEGER NOT NULL,
@ -96,10 +96,10 @@ impl WorkspaceDb {
WorkspaceLocation, WorkspaceLocation,
bool, bool,
DockPosition, DockPosition,
) = ) =
self.select_row_bound(sql!{ self.select_row_bound(sql!{
SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor
FROM workspaces FROM workspaces
WHERE workspace_location = ? WHERE workspace_location = ?
}) })
.and_then(|mut prepared_statement| (prepared_statement)(&workspace_location)) .and_then(|mut prepared_statement| (prepared_statement)(&workspace_location))
@ -119,7 +119,7 @@ impl WorkspaceDb {
.context("Getting center group") .context("Getting center group")
.log_err()?, .log_err()?,
dock_position, dock_position,
left_sidebar_open left_sidebar_open,
}) })
} }
@ -158,7 +158,12 @@ impl WorkspaceDb {
dock_visible = ?4, dock_visible = ?4,
dock_anchor = ?5, dock_anchor = ?5,
timestamp = CURRENT_TIMESTAMP timestamp = CURRENT_TIMESTAMP
))?((workspace.id, &workspace.location, workspace.left_sidebar_open, workspace.dock_position)) ))?((
workspace.id,
&workspace.location,
workspace.left_sidebar_open,
workspace.dock_position,
))
.context("Updating workspace")?; .context("Updating workspace")?;
// Save center pane group and dock pane // Save center pane group and dock pane
@ -191,20 +196,20 @@ impl WorkspaceDb {
query! { query! {
fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> { fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
SELECT workspace_id, workspace_location SELECT workspace_id, workspace_location
FROM workspaces FROM workspaces
WHERE workspace_location IS NOT NULL WHERE workspace_location IS NOT NULL
ORDER BY timestamp DESC ORDER BY timestamp DESC
} }
} }
query! { query! {
async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> {
DELETE FROM workspaces DELETE FROM workspaces
WHERE workspace_id IS ? WHERE workspace_id IS ?
} }
} }
// Returns the recent locations which are still valid on disk and deletes ones which no longer // Returns the recent locations which are still valid on disk and deletes ones which no longer
// exist. // exist.
pub async fn recent_workspaces_on_disk(&self) -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> { pub async fn recent_workspaces_on_disk(&self) -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
@ -217,7 +222,7 @@ impl WorkspaceDb {
delete_tasks.push(self.delete_stale_workspace(id)); delete_tasks.push(self.delete_stale_workspace(id));
} }
} }
futures::future::join_all(delete_tasks).await; futures::future::join_all(delete_tasks).await;
Ok(result) Ok(result)
} }
@ -233,10 +238,16 @@ impl WorkspaceDb {
} }
fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> { fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
Ok(self.get_pane_group(workspace_id, None)? Ok(self
.get_pane_group(workspace_id, None)?
.into_iter() .into_iter()
.next() .next()
.unwrap_or_else(|| SerializedPaneGroup::Pane(SerializedPane { active: true, children: vec![] }))) .unwrap_or_else(|| {
SerializedPaneGroup::Pane(SerializedPane {
active: true,
children: vec![],
})
}))
} }
fn get_pane_group( fn get_pane_group(
@ -248,7 +259,7 @@ impl WorkspaceDb {
type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>); type GroupOrPane = (Option<GroupId>, Option<Axis>, Option<PaneId>, Option<bool>);
self.select_bound::<GroupKey, GroupOrPane>(sql!( self.select_bound::<GroupKey, GroupOrPane>(sql!(
SELECT group_id, axis, pane_id, active SELECT group_id, axis, pane_id, active
FROM (SELECT FROM (SELECT
group_id, group_id,
axis, axis,
NULL as pane_id, NULL as pane_id,
@ -256,18 +267,18 @@ impl WorkspaceDb {
position, position,
parent_group_id, parent_group_id,
workspace_id workspace_id
FROM pane_groups FROM pane_groups
UNION UNION
SELECT SELECT
NULL,
NULL, NULL,
NULL,
center_panes.pane_id, center_panes.pane_id,
panes.active as active, panes.active as active,
position, position,
parent_group_id, parent_group_id,
panes.workspace_id as workspace_id panes.workspace_id as workspace_id
FROM center_panes FROM center_panes
JOIN panes ON center_panes.pane_id = panes.pane_id) JOIN panes ON center_panes.pane_id = panes.pane_id)
WHERE parent_group_id IS ? AND workspace_id = ? WHERE parent_group_id IS ? AND workspace_id = ?
ORDER BY position ORDER BY position
))?((group_id, workspace_id))? ))?((group_id, workspace_id))?
@ -290,13 +301,12 @@ impl WorkspaceDb {
// Filter out panes and pane groups which don't have any children or items // Filter out panes and pane groups which don't have any children or items
.filter(|pane_group| match pane_group { .filter(|pane_group| match pane_group {
Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(), Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(), Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
_ => true, _ => true,
}) })
.collect::<Result<_>>() .collect::<Result<_>>()
} }
fn save_pane_group( fn save_pane_group(
conn: &Connection, conn: &Connection,
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
@ -308,15 +318,10 @@ impl WorkspaceDb {
let (parent_id, position) = unzip_option(parent); let (parent_id, position) = unzip_option(parent);
let group_id = conn.select_row_bound::<_, i64>(sql!( let group_id = conn.select_row_bound::<_, i64>(sql!(
INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
RETURNING group_id RETURNING group_id
))?(( ))?((workspace_id, parent_id, position, *axis))?
workspace_id,
parent_id,
position,
*axis,
))?
.ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
for (position, group) in children.iter().enumerate() { for (position, group) in children.iter().enumerate() {
@ -337,9 +342,7 @@ impl WorkspaceDb {
SELECT pane_id, active SELECT pane_id, active
FROM panes FROM panes
WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?)
))?( ))?(workspace_id)?
workspace_id,
)?
.context("No dock pane for workspace")?; .context("No dock pane for workspace")?;
Ok(SerializedPane::new( Ok(SerializedPane::new(
@ -356,8 +359,8 @@ impl WorkspaceDb {
dock: bool, dock: bool,
) -> Result<PaneId> { ) -> Result<PaneId> {
let pane_id = conn.select_row_bound::<_, i64>(sql!( let pane_id = conn.select_row_bound::<_, i64>(sql!(
INSERT INTO panes(workspace_id, active) INSERT INTO panes(workspace_id, active)
VALUES (?, ?) VALUES (?, ?)
RETURNING pane_id RETURNING pane_id
))?((workspace_id, pane.active))? ))?((workspace_id, pane.active))?
.ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
@ -399,14 +402,13 @@ impl WorkspaceDb {
Ok(()) Ok(())
} }
query!{ query! {
pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> {
UPDATE workspaces UPDATE workspaces
SET timestamp = CURRENT_TIMESTAMP SET timestamp = CURRENT_TIMESTAMP
WHERE workspace_id = ? WHERE workspace_id = ?
} }
} }
} }
#[cfg(test)] #[cfg(test)]
@ -495,7 +497,7 @@ mod tests {
dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom), dock_position: crate::dock::DockPosition::Shown(DockAnchor::Bottom),
center_group: Default::default(), center_group: Default::default(),
dock_pane: Default::default(), dock_pane: Default::default(),
left_sidebar_open: true left_sidebar_open: true,
}; };
let mut workspace_2 = SerializedWorkspace { let mut workspace_2 = SerializedWorkspace {
@ -504,7 +506,7 @@ mod tests {
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded), dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Expanded),
center_group: Default::default(), center_group: Default::default(),
dock_pane: Default::default(), dock_pane: Default::default(),
left_sidebar_open: false left_sidebar_open: false,
}; };
db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1.clone()).await;
@ -610,7 +612,7 @@ mod tests {
dock_position: DockPosition::Shown(DockAnchor::Bottom), dock_position: DockPosition::Shown(DockAnchor::Bottom),
center_group, center_group,
dock_pane, dock_pane,
left_sidebar_open: true left_sidebar_open: true,
}; };
db.save_workspace(workspace.clone()).await; db.save_workspace(workspace.clone()).await;
@ -683,7 +685,7 @@ mod tests {
dock_position: DockPosition::Shown(DockAnchor::Right), dock_position: DockPosition::Shown(DockAnchor::Right),
center_group: Default::default(), center_group: Default::default(),
dock_pane: Default::default(), dock_pane: Default::default(),
left_sidebar_open: false left_sidebar_open: false,
}; };
db.save_workspace(workspace_3.clone()).await; db.save_workspace(workspace_3.clone()).await;
@ -718,7 +720,7 @@ mod tests {
dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right), dock_position: crate::dock::DockPosition::Hidden(DockAnchor::Right),
center_group: center_group.clone(), center_group: center_group.clone(),
dock_pane, dock_pane,
left_sidebar_open: true left_sidebar_open: true,
} }
} }