Done first draft of strongly typed migrations
This commit is contained in:
parent
4a00f0b062
commit
c84201fc9f
18 changed files with 396 additions and 448 deletions
|
@ -2,7 +2,7 @@ mod update_notification;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN};
|
use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN};
|
||||||
use db::Db;
|
use db::{kvp::KeyValue, Db};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||||
MutableAppContext, Task, WeakViewHandle,
|
MutableAppContext, Task, WeakViewHandle,
|
||||||
|
@ -42,7 +42,7 @@ pub struct AutoUpdater {
|
||||||
current_version: AppVersion,
|
current_version: AppVersion,
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
pending_poll: Option<Task<()>>,
|
pending_poll: Option<Task<()>>,
|
||||||
db: project::Db,
|
db: project::Db<KeyValue>,
|
||||||
server_url: String,
|
server_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ impl Entity for AutoUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
db: Db,
|
db: Db<KeyValue>,
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
server_url: String,
|
server_url: String,
|
||||||
cx: &mut MutableAppContext,
|
cx: &mut MutableAppContext,
|
||||||
|
@ -126,7 +126,7 @@ impl AutoUpdater {
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
current_version: AppVersion,
|
current_version: AppVersion,
|
||||||
db: project::Db,
|
db: project::Db<KeyValue>,
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
server_url: String,
|
server_url: String,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use async_tungstenite::tungstenite::{
|
||||||
error::Error as WebsocketError,
|
error::Error as WebsocketError,
|
||||||
http::{Request, StatusCode},
|
http::{Request, StatusCode},
|
||||||
};
|
};
|
||||||
use db::Db;
|
use db::{kvp::KeyValue, Db};
|
||||||
use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
|
@ -1218,7 +1218,7 @@ impl Client {
|
||||||
self.peer.respond_with_error(receipt, error)
|
self.peer.respond_with_error(receipt, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_telemetry(&self, db: Db) {
|
pub fn start_telemetry(&self, db: Db<KeyValue>) {
|
||||||
self.telemetry.start(db.clone());
|
self.telemetry.start(db.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::http::HttpClient;
|
use crate::http::HttpClient;
|
||||||
use db::Db;
|
use db::{kvp::KeyValue, Db};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
executor::Background,
|
executor::Background,
|
||||||
serde_json::{self, value::Map, Value},
|
serde_json::{self, value::Map, Value},
|
||||||
|
@ -148,7 +148,7 @@ impl Telemetry {
|
||||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(self: &Arc<Self>, db: Db) {
|
pub fn start(self: &Arc<Self>, db: Db<KeyValue>) {
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
self.executor
|
self.executor
|
||||||
.spawn(
|
.spawn(
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
use std::{fs::File, path::Path};
|
|
||||||
|
|
||||||
const TEST_FILE: &'static str = "test-db.db";
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let db = db::Db::open_in_memory("db");
|
|
||||||
|
|
||||||
let file = Path::new(TEST_FILE);
|
|
||||||
|
|
||||||
let f = File::create(file)?;
|
|
||||||
drop(f);
|
|
||||||
|
|
||||||
// let workspace_1 = db.workspace_for_roots(&["/tmp"]);
|
|
||||||
// let workspace_2 = db.workspace_for_roots(&["/tmp", "/tmp2"]);
|
|
||||||
// let workspace_3 = db.workspace_for_roots(&["/tmp3", "/tmp2"]);
|
|
||||||
|
|
||||||
// db.save_dock_pane(
|
|
||||||
// &workspace_1.workspace_id,
|
|
||||||
// &SerializedDockPane {
|
|
||||||
// anchor_position: DockAnchor::Expanded,
|
|
||||||
// visible: true,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// db.save_dock_pane(
|
|
||||||
// &workspace_2.workspace_id,
|
|
||||||
// &SerializedDockPane {
|
|
||||||
// anchor_position: DockAnchor::Bottom,
|
|
||||||
// visible: true,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// db.save_dock_pane(
|
|
||||||
// &workspace_3.workspace_id,
|
|
||||||
// &SerializedDockPane {
|
|
||||||
// anchor_position: DockAnchor::Right,
|
|
||||||
// visible: false,
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
db.write_to(file).ok();
|
|
||||||
|
|
||||||
println!("Wrote database!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
use std::{fs::File, path::Path};
|
|
||||||
|
|
||||||
const TEST_FILE: &'static str = "test-db.db";
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
env_logger::init();
|
|
||||||
let db = db::Db::open_in_memory("db");
|
|
||||||
|
|
||||||
let file = Path::new(TEST_FILE);
|
|
||||||
|
|
||||||
let f = File::create(file)?;
|
|
||||||
drop(f);
|
|
||||||
|
|
||||||
db.write_kvp("test", "1")?;
|
|
||||||
db.write_kvp("test-2", "2")?;
|
|
||||||
|
|
||||||
db.workspace_for_roots(&["/tmp1"]);
|
|
||||||
db.workspace_for_roots(&["/tmp1", "/tmp2"]);
|
|
||||||
db.workspace_for_roots(&["/tmp1", "/tmp2", "/tmp3"]);
|
|
||||||
db.workspace_for_roots(&["/tmp2", "/tmp3"]);
|
|
||||||
db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"]);
|
|
||||||
db.workspace_for_roots(&["/tmp2", "/tmp4"]);
|
|
||||||
db.workspace_for_roots(&["/tmp2"]);
|
|
||||||
|
|
||||||
db.write_to(file).ok();
|
|
||||||
|
|
||||||
println!("Wrote database!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -7,18 +7,23 @@ use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use kvp::KVP_MIGRATION;
|
|
||||||
use sqlez::connection::Connection;
|
use sqlez::connection::Connection;
|
||||||
|
use sqlez::domain::Domain;
|
||||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||||
use workspace::items::ITEM_MIGRATIONS;
|
|
||||||
use workspace::pane::PANE_MIGRATIONS;
|
|
||||||
|
|
||||||
pub use workspace::*;
|
pub use workspace::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
const INITIALIZE_QUERY: &'static str = indoc! {"
|
||||||
pub struct Db(ThreadSafeConnection);
|
PRAGMA journal_mode=WAL;
|
||||||
|
PRAGMA synchronous=NORMAL;
|
||||||
|
PRAGMA foreign_keys=TRUE;
|
||||||
|
PRAGMA case_sensitive_like=TRUE;
|
||||||
|
"};
|
||||||
|
|
||||||
impl Deref for Db {
|
#[derive(Clone)]
|
||||||
|
pub struct Db<D: Domain>(ThreadSafeConnection<D>);
|
||||||
|
|
||||||
|
impl<D: Domain> Deref for Db<D> {
|
||||||
type Target = sqlez::connection::Connection;
|
type Target = sqlez::connection::Connection;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -26,7 +31,7 @@ impl Deref for Db {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db {
|
impl<D: Domain> Db<D> {
|
||||||
/// Open or create a database at the given directory path.
|
/// Open or create a database at the given directory path.
|
||||||
pub fn open(db_dir: &Path, channel: &'static str) -> Self {
|
pub fn open(db_dir: &Path, channel: &'static str) -> Self {
|
||||||
// Use 0 for now. Will implement incrementing and clearing of old db files soon TM
|
// Use 0 for now. Will implement incrementing and clearing of old db files soon TM
|
||||||
|
@ -35,17 +40,15 @@ impl Db {
|
||||||
.expect("Should be able to create the database directory");
|
.expect("Should be able to create the database directory");
|
||||||
let db_path = current_db_dir.join(Path::new("db.sqlite"));
|
let db_path = current_db_dir.join(Path::new("db.sqlite"));
|
||||||
|
|
||||||
Db(initialize_connection(ThreadSafeConnection::new(
|
Db(
|
||||||
db_path.to_string_lossy().as_ref(),
|
ThreadSafeConnection::new(db_path.to_string_lossy().as_ref(), true)
|
||||||
true,
|
.with_initialize_query(INITIALIZE_QUERY),
|
||||||
)))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open a in memory database for testing and as a fallback.
|
/// Open a in memory database for testing and as a fallback.
|
||||||
pub fn open_in_memory(db_name: &str) -> Self {
|
pub fn open_in_memory(db_name: &str) -> Self {
|
||||||
Db(initialize_connection(ThreadSafeConnection::new(
|
Db(ThreadSafeConnection::new(db_name, false).with_initialize_query(INITIALIZE_QUERY))
|
||||||
db_name, false,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persisting(&self) -> bool {
|
pub fn persisting(&self) -> bool {
|
||||||
|
@ -56,19 +59,8 @@ impl Db {
|
||||||
let destination = Connection::open_file(dest.as_ref().to_string_lossy().as_ref());
|
let destination = Connection::open_file(dest.as_ref().to_string_lossy().as_ref());
|
||||||
self.backup_main(&destination)
|
self.backup_main(&destination)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize_connection(conn: ThreadSafeConnection) -> ThreadSafeConnection {
|
pub fn open_as<D2: Domain>(&self) -> Db<D2> {
|
||||||
conn.with_initialize_query(indoc! {"
|
Db(self.0.for_domain())
|
||||||
PRAGMA journal_mode=WAL;
|
}
|
||||||
PRAGMA synchronous=NORMAL;
|
|
||||||
PRAGMA foreign_keys=TRUE;
|
|
||||||
PRAGMA case_sensitive_like=TRUE;
|
|
||||||
"})
|
|
||||||
.with_migrations(&[
|
|
||||||
KVP_MIGRATION,
|
|
||||||
WORKSPACES_MIGRATION,
|
|
||||||
PANE_MIGRATIONS,
|
|
||||||
ITEM_MIGRATIONS,
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::Db;
|
use super::Db;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use sqlez::migrations::Migration;
|
use sqlez::{connection::Connection, domain::Domain, migrations::Migration};
|
||||||
|
|
||||||
pub(crate) const KVP_MIGRATION: Migration = Migration::new(
|
pub(crate) const KVP_MIGRATION: Migration = Migration::new(
|
||||||
"kvp",
|
"kvp",
|
||||||
|
@ -13,7 +13,16 @@ pub(crate) const KVP_MIGRATION: Migration = Migration::new(
|
||||||
"}],
|
"}],
|
||||||
);
|
);
|
||||||
|
|
||||||
impl Db {
|
#[derive(Clone)]
|
||||||
|
pub enum KeyValue {}
|
||||||
|
|
||||||
|
impl Domain for KeyValue {
|
||||||
|
fn migrate(conn: &Connection) -> anyhow::Result<()> {
|
||||||
|
KVP_MIGRATION.run(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Db<KeyValue> {
|
||||||
pub fn read_kvp(&self, key: &str) -> Result<Option<String>> {
|
pub fn read_kvp(&self, key: &str) -> Result<Option<String>> {
|
||||||
self.select_row_bound("SELECT value FROM kv_store WHERE key = (?)")?(key)
|
self.select_row_bound("SELECT value FROM kv_store WHERE key = (?)")?(key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,24 @@
|
||||||
pub(crate) mod items;
|
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub(crate) mod pane;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::{bail, Context, Result};
|
||||||
use util::{iife, ResultExt};
|
use util::{iife, unzip_option, ResultExt};
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use sqlez::migrations::Migration;
|
use sqlez::{domain::Domain, migrations::Migration};
|
||||||
|
|
||||||
|
use self::model::{
|
||||||
|
Axis, GroupId, PaneId, SerializedItem, SerializedItemKind, SerializedPane, SerializedPaneGroup,
|
||||||
|
SerializedWorkspace, WorkspaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Db;
|
||||||
|
|
||||||
|
// 1) Move all of this into Workspace crate
|
||||||
|
// 2) Deserialize items fully
|
||||||
|
// 3) Typed prepares (including how you expect to pull data out)
|
||||||
|
// 4) Investigate Tree column impls
|
||||||
|
|
||||||
pub(crate) const WORKSPACES_MIGRATION: Migration = Migration::new(
|
pub(crate) const WORKSPACES_MIGRATION: Migration = Migration::new(
|
||||||
"workspace",
|
"workspace",
|
||||||
|
@ -22,11 +32,58 @@ pub(crate) const WORKSPACES_MIGRATION: Migration = Migration::new(
|
||||||
"}],
|
"}],
|
||||||
);
|
);
|
||||||
|
|
||||||
use self::model::{SerializedWorkspace, WorkspaceId};
|
pub(crate) const PANE_MIGRATIONS: Migration = Migration::new(
|
||||||
|
"pane",
|
||||||
|
&[indoc! {"
|
||||||
|
CREATE TABLE pane_groups(
|
||||||
|
group_id INTEGER PRIMARY KEY,
|
||||||
|
workspace_id BLOB NOT NULL,
|
||||||
|
parent_group_id INTEGER, -- NULL indicates that this is a root node
|
||||||
|
position INTEGER, -- NULL indicates that this is a root node
|
||||||
|
axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
|
||||||
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE panes(
|
||||||
|
pane_id INTEGER PRIMARY KEY,
|
||||||
|
workspace_id BLOB NOT NULL,
|
||||||
|
parent_group_id INTEGER, -- NULL, this is a dock pane
|
||||||
|
position INTEGER, -- NULL, this is a dock pane
|
||||||
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
||||||
|
) STRICT;
|
||||||
|
"}],
|
||||||
|
);
|
||||||
|
|
||||||
use super::Db;
|
pub(crate) const ITEM_MIGRATIONS: Migration = Migration::new(
|
||||||
|
"item",
|
||||||
|
&[indoc! {"
|
||||||
|
CREATE TABLE items(
|
||||||
|
item_id INTEGER NOT NULL, -- This is the item's view id, so this is not unique
|
||||||
|
workspace_id BLOB NOT NULL,
|
||||||
|
pane_id INTEGER NOT NULL,
|
||||||
|
kind TEXT NOT NULL,
|
||||||
|
position INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
|
||||||
|
FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE
|
||||||
|
PRIMARY KEY(item_id, workspace_id)
|
||||||
|
) STRICT;
|
||||||
|
"}],
|
||||||
|
);
|
||||||
|
|
||||||
impl Db {
|
#[derive(Clone)]
|
||||||
|
pub enum Workspace {}
|
||||||
|
|
||||||
|
impl Domain for Workspace {
|
||||||
|
fn migrate(conn: &sqlez::connection::Connection) -> anyhow::Result<()> {
|
||||||
|
WORKSPACES_MIGRATION.run(&conn)?;
|
||||||
|
PANE_MIGRATIONS.run(&conn)?;
|
||||||
|
ITEM_MIGRATIONS.run(&conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Db<Workspace> {
|
||||||
/// Returns a serialized workspace for the given worktree_roots. If the passed array
|
/// Returns a serialized workspace for the given worktree_roots. If the passed array
|
||||||
/// is empty, the most recent workspace is returned instead. If no workspace for the
|
/// is empty, the most recent workspace is returned instead. If no workspace for the
|
||||||
/// passed roots is stored, returns none.
|
/// passed roots is stored, returns none.
|
||||||
|
@ -129,6 +186,142 @@ impl Db {
|
||||||
.log_err()
|
.log_err()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_center_pane_group(
|
||||||
|
&self,
|
||||||
|
workspace_id: &WorkspaceId,
|
||||||
|
) -> Result<SerializedPaneGroup> {
|
||||||
|
self.get_pane_group_children(workspace_id, None)?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.context("No center pane group")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pane_group_children<'a>(
|
||||||
|
&self,
|
||||||
|
workspace_id: &WorkspaceId,
|
||||||
|
group_id: Option<GroupId>,
|
||||||
|
) -> Result<Vec<SerializedPaneGroup>> {
|
||||||
|
self.select_bound::<(Option<GroupId>, &WorkspaceId), (Option<GroupId>, Option<Axis>, Option<PaneId>)>(indoc! {"
|
||||||
|
SELECT group_id, axis, pane_id
|
||||||
|
FROM (SELECT group_id, axis, NULL as pane_id, position, parent_group_id, workspace_id
|
||||||
|
FROM pane_groups
|
||||||
|
UNION
|
||||||
|
SELECT NULL, NULL, pane_id, position, parent_group_id, workspace_id
|
||||||
|
FROM panes
|
||||||
|
-- Remove the dock panes from the union
|
||||||
|
WHERE parent_group_id IS NOT NULL and position IS NOT NULL)
|
||||||
|
WHERE parent_group_id IS ? AND workspace_id = ?
|
||||||
|
ORDER BY position
|
||||||
|
"})?((group_id, workspace_id))?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(group_id, axis, pane_id)| {
|
||||||
|
if let Some((group_id, axis)) = group_id.zip(axis) {
|
||||||
|
Ok(SerializedPaneGroup::Group {
|
||||||
|
axis,
|
||||||
|
children: self.get_pane_group_children(
|
||||||
|
workspace_id,
|
||||||
|
Some(group_id),
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
} else if let Some(pane_id) = pane_id {
|
||||||
|
Ok(SerializedPaneGroup::Pane(SerializedPane {
|
||||||
|
children: self.get_items(pane_id)?,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
bail!("Pane Group Child was neither a pane group or a pane");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_pane_group(
|
||||||
|
&self,
|
||||||
|
workspace_id: &WorkspaceId,
|
||||||
|
pane_group: &SerializedPaneGroup,
|
||||||
|
parent: Option<(GroupId, usize)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if parent.is_none() && !matches!(pane_group, SerializedPaneGroup::Group { .. }) {
|
||||||
|
bail!("Pane groups must have a SerializedPaneGroup::Group at the root")
|
||||||
|
}
|
||||||
|
|
||||||
|
let (parent_id, position) = unzip_option(parent);
|
||||||
|
|
||||||
|
match pane_group {
|
||||||
|
SerializedPaneGroup::Group { axis, children } => {
|
||||||
|
let parent_id = self.insert_bound("INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?)")?
|
||||||
|
((workspace_id, parent_id, position, *axis))?;
|
||||||
|
|
||||||
|
for (position, group) in children.iter().enumerate() {
|
||||||
|
self.save_pane_group(workspace_id, group, Some((parent_id, position)))?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
SerializedPaneGroup::Pane(pane) => self.save_pane(workspace_id, pane, parent),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_dock_pane(&self, workspace_id: &WorkspaceId) -> Result<SerializedPane> {
|
||||||
|
let pane_id = self.select_row_bound(indoc! {"
|
||||||
|
SELECT pane_id FROM panes
|
||||||
|
WHERE workspace_id = ? AND parent_group_id IS NULL AND position IS NULL"})?(
|
||||||
|
workspace_id,
|
||||||
|
)?
|
||||||
|
.context("No dock pane for workspace")?;
|
||||||
|
|
||||||
|
Ok(SerializedPane::new(
|
||||||
|
self.get_items(pane_id).context("Reading items")?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_pane(
|
||||||
|
&self,
|
||||||
|
workspace_id: &WorkspaceId,
|
||||||
|
pane: &SerializedPane,
|
||||||
|
parent: Option<(GroupId, usize)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (parent_id, order) = unzip_option(parent);
|
||||||
|
|
||||||
|
let pane_id = self.insert_bound(
|
||||||
|
"INSERT INTO panes(workspace_id, parent_group_id, position) VALUES (?, ?, ?)",
|
||||||
|
)?((workspace_id, parent_id, order))?;
|
||||||
|
|
||||||
|
self.save_items(workspace_id, pane_id, &pane.children)
|
||||||
|
.context("Saving items")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
|
||||||
|
Ok(self.select_bound(indoc! {"
|
||||||
|
SELECT item_id, kind FROM items
|
||||||
|
WHERE pane_id = ?
|
||||||
|
ORDER BY position"})?(pane_id)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(item_id, kind)| match kind {
|
||||||
|
SerializedItemKind::Terminal => SerializedItem::Terminal { item_id },
|
||||||
|
_ => unimplemented!(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_items(
|
||||||
|
&self,
|
||||||
|
workspace_id: &WorkspaceId,
|
||||||
|
pane_id: PaneId,
|
||||||
|
items: &[SerializedItem],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut delete_old = self
|
||||||
|
.exec_bound("DELETE FROM items WHERE workspace_id = ? AND pane_id = ? AND item_id = ?")
|
||||||
|
.context("Preparing deletion")?;
|
||||||
|
let mut insert_new = self.exec_bound(
|
||||||
|
"INSERT INTO items(item_id, workspace_id, pane_id, kind, position) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
).context("Preparing insertion")?;
|
||||||
|
for (position, item) in items.iter().enumerate() {
|
||||||
|
delete_old((workspace_id, pane_id, item.item_id()))?;
|
||||||
|
insert_new((item.item_id(), workspace_id, pane_id, item.kind(), position))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -214,4 +407,89 @@ mod tests {
|
||||||
workspace_3
|
workspace_3
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use crate::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
|
||||||
|
|
||||||
|
fn default_workspace(
|
||||||
|
dock_pane: SerializedPane,
|
||||||
|
center_group: &SerializedPaneGroup,
|
||||||
|
) -> SerializedWorkspace {
|
||||||
|
SerializedWorkspace {
|
||||||
|
dock_anchor: crate::model::DockAnchor::Right,
|
||||||
|
dock_visible: false,
|
||||||
|
center_group: center_group.clone(),
|
||||||
|
dock_pane,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_dock_pane() {
|
||||||
|
env_logger::try_init().ok();
|
||||||
|
|
||||||
|
let db = Db::open_in_memory("basic_dock_pane");
|
||||||
|
|
||||||
|
let dock_pane = crate::model::SerializedPane {
|
||||||
|
children: vec![
|
||||||
|
SerializedItem::Terminal { item_id: 1 },
|
||||||
|
SerializedItem::Terminal { item_id: 4 },
|
||||||
|
SerializedItem::Terminal { item_id: 2 },
|
||||||
|
SerializedItem::Terminal { item_id: 3 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let workspace = default_workspace(dock_pane, &Default::default());
|
||||||
|
|
||||||
|
db.save_workspace(&["/tmp"], None, &workspace);
|
||||||
|
|
||||||
|
let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_split() {
|
||||||
|
env_logger::try_init().ok();
|
||||||
|
|
||||||
|
let db = Db::open_in_memory("simple_split");
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
// | 1,2 | 5,6 |
|
||||||
|
// | - - - | |
|
||||||
|
// | 3,4 | |
|
||||||
|
// -----------------
|
||||||
|
let center_pane = SerializedPaneGroup::Group {
|
||||||
|
axis: crate::model::Axis::Horizontal,
|
||||||
|
children: vec![
|
||||||
|
SerializedPaneGroup::Group {
|
||||||
|
axis: crate::model::Axis::Vertical,
|
||||||
|
children: vec![
|
||||||
|
SerializedPaneGroup::Pane(SerializedPane {
|
||||||
|
children: vec![
|
||||||
|
SerializedItem::Terminal { item_id: 1 },
|
||||||
|
SerializedItem::Terminal { item_id: 2 },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
SerializedPaneGroup::Pane(SerializedPane {
|
||||||
|
children: vec![
|
||||||
|
SerializedItem::Terminal { item_id: 4 },
|
||||||
|
SerializedItem::Terminal { item_id: 3 },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
SerializedPaneGroup::Pane(SerializedPane {
|
||||||
|
children: vec![
|
||||||
|
SerializedItem::Terminal { item_id: 5 },
|
||||||
|
SerializedItem::Terminal { item_id: 6 },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let workspace = default_workspace(Default::default(), ¢er_pane);
|
||||||
|
|
||||||
|
db.save_workspace(&["/tmp"], None, &workspace);
|
||||||
|
|
||||||
|
assert_eq!(workspace.center_group, center_pane);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use indoc::indoc;
|
|
||||||
use sqlez::migrations::Migration;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
model::{PaneId, SerializedItem, SerializedItemKind, WorkspaceId},
|
|
||||||
Db,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1) Move all of this into Workspace crate
|
|
||||||
// 2) Deserialize items fully
|
|
||||||
// 3) Typed prepares (including how you expect to pull data out)
|
|
||||||
// 4) Investigate Tree column impls
|
|
||||||
pub(crate) const ITEM_MIGRATIONS: Migration = Migration::new(
|
|
||||||
"item",
|
|
||||||
&[indoc! {"
|
|
||||||
CREATE TABLE items(
|
|
||||||
item_id INTEGER NOT NULL, -- This is the item's view id, so this is not unique
|
|
||||||
workspace_id BLOB NOT NULL,
|
|
||||||
pane_id INTEGER NOT NULL,
|
|
||||||
kind TEXT NOT NULL,
|
|
||||||
position INTEGER NOT NULL,
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE
|
|
||||||
FOREIGN KEY(pane_id) REFERENCES panes(pane_id) ON DELETE CASCADE
|
|
||||||
PRIMARY KEY(item_id, workspace_id)
|
|
||||||
) STRICT;
|
|
||||||
"}],
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Db {
|
|
||||||
pub(crate) fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
|
|
||||||
Ok(self.select_bound(indoc! {"
|
|
||||||
SELECT item_id, kind FROM items
|
|
||||||
WHERE pane_id = ?
|
|
||||||
ORDER BY position"})?(pane_id)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(item_id, kind)| match kind {
|
|
||||||
SerializedItemKind::Terminal => SerializedItem::Terminal { item_id },
|
|
||||||
_ => unimplemented!(),
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn save_items(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
pane_id: PaneId,
|
|
||||||
items: &[SerializedItem],
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut delete_old = self
|
|
||||||
.exec_bound("DELETE FROM items WHERE workspace_id = ? AND pane_id = ? AND item_id = ?")
|
|
||||||
.context("Preparing deletion")?;
|
|
||||||
let mut insert_new = self.exec_bound(
|
|
||||||
"INSERT INTO items(item_id, workspace_id, pane_id, kind, position) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
).context("Preparing insertion")?;
|
|
||||||
for (position, item) in items.iter().enumerate() {
|
|
||||||
delete_old((workspace_id, pane_id, item.item_id()))?;
|
|
||||||
insert_new((item.item_id(), workspace_id, pane_id, item.kind(), position))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,232 +0,0 @@
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use indoc::indoc;
|
|
||||||
use sqlez::migrations::Migration;
|
|
||||||
use util::unzip_option;
|
|
||||||
|
|
||||||
use crate::model::{Axis, GroupId, PaneId, SerializedPane};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
model::{SerializedPaneGroup, WorkspaceId},
|
|
||||||
Db,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) const PANE_MIGRATIONS: Migration = Migration::new(
|
|
||||||
"pane",
|
|
||||||
&[indoc! {"
|
|
||||||
CREATE TABLE pane_groups(
|
|
||||||
group_id INTEGER PRIMARY KEY,
|
|
||||||
workspace_id BLOB NOT NULL,
|
|
||||||
parent_group_id INTEGER, -- NULL indicates that this is a root node
|
|
||||||
position INTEGER, -- NULL indicates that this is a root node
|
|
||||||
axis TEXT NOT NULL, -- Enum: 'Vertical' / 'Horizontal'
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
|
||||||
) STRICT;
|
|
||||||
|
|
||||||
CREATE TABLE panes(
|
|
||||||
pane_id INTEGER PRIMARY KEY,
|
|
||||||
workspace_id BLOB NOT NULL,
|
|
||||||
parent_group_id INTEGER, -- NULL, this is a dock pane
|
|
||||||
position INTEGER, -- NULL, this is a dock pane
|
|
||||||
FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
|
|
||||||
) STRICT;
|
|
||||||
"}],
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Db {
|
|
||||||
pub(crate) fn get_center_pane_group(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
) -> Result<SerializedPaneGroup> {
|
|
||||||
self.get_pane_group_children(workspace_id, None)?
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.context("No center pane group")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pane_group_children<'a>(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
group_id: Option<GroupId>,
|
|
||||||
) -> Result<Vec<SerializedPaneGroup>> {
|
|
||||||
self.select_bound::<(Option<GroupId>, &WorkspaceId), (Option<GroupId>, Option<Axis>, Option<PaneId>)>(indoc! {"
|
|
||||||
SELECT group_id, axis, pane_id
|
|
||||||
FROM (SELECT group_id, axis, NULL as pane_id, position, parent_group_id, workspace_id
|
|
||||||
FROM pane_groups
|
|
||||||
UNION
|
|
||||||
SELECT NULL, NULL, pane_id, position, parent_group_id, workspace_id
|
|
||||||
FROM panes
|
|
||||||
-- Remove the dock panes from the union
|
|
||||||
WHERE parent_group_id IS NOT NULL and position IS NOT NULL)
|
|
||||||
WHERE parent_group_id IS ? AND workspace_id = ?
|
|
||||||
ORDER BY position
|
|
||||||
"})?((group_id, workspace_id))?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(group_id, axis, pane_id)| {
|
|
||||||
if let Some((group_id, axis)) = group_id.zip(axis) {
|
|
||||||
Ok(SerializedPaneGroup::Group {
|
|
||||||
axis,
|
|
||||||
children: self.get_pane_group_children(
|
|
||||||
workspace_id,
|
|
||||||
Some(group_id),
|
|
||||||
)?,
|
|
||||||
})
|
|
||||||
} else if let Some(pane_id) = pane_id {
|
|
||||||
Ok(SerializedPaneGroup::Pane(SerializedPane {
|
|
||||||
children: self.get_items(pane_id)?,
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
bail!("Pane Group Child was neither a pane group or a pane");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn save_pane_group(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
pane_group: &SerializedPaneGroup,
|
|
||||||
parent: Option<(GroupId, usize)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if parent.is_none() && !matches!(pane_group, SerializedPaneGroup::Group { .. }) {
|
|
||||||
bail!("Pane groups must have a SerializedPaneGroup::Group at the root")
|
|
||||||
}
|
|
||||||
|
|
||||||
let (parent_id, position) = unzip_option(parent);
|
|
||||||
|
|
||||||
match pane_group {
|
|
||||||
SerializedPaneGroup::Group { axis, children } => {
|
|
||||||
let parent_id = self.insert_bound("INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?)")?
|
|
||||||
((workspace_id, parent_id, position, *axis))?;
|
|
||||||
|
|
||||||
for (position, group) in children.iter().enumerate() {
|
|
||||||
self.save_pane_group(workspace_id, group, Some((parent_id, position)))?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
SerializedPaneGroup::Pane(pane) => self.save_pane(workspace_id, pane, parent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_dock_pane(&self, workspace_id: &WorkspaceId) -> Result<SerializedPane> {
|
|
||||||
let pane_id = self.select_row_bound(indoc! {"
|
|
||||||
SELECT pane_id FROM panes
|
|
||||||
WHERE workspace_id = ? AND parent_group_id IS NULL AND position IS NULL"})?(
|
|
||||||
workspace_id,
|
|
||||||
)?
|
|
||||||
.context("No dock pane for workspace")?;
|
|
||||||
|
|
||||||
Ok(SerializedPane::new(
|
|
||||||
self.get_items(pane_id).context("Reading items")?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn save_pane(
|
|
||||||
&self,
|
|
||||||
workspace_id: &WorkspaceId,
|
|
||||||
pane: &SerializedPane,
|
|
||||||
parent: Option<(GroupId, usize)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let (parent_id, order) = unzip_option(parent);
|
|
||||||
|
|
||||||
let pane_id = self.insert_bound(
|
|
||||||
"INSERT INTO panes(workspace_id, parent_group_id, position) VALUES (?, ?, ?)",
|
|
||||||
)?((workspace_id, parent_id, order))?;
|
|
||||||
|
|
||||||
self.save_items(workspace_id, pane_id, &pane.children)
|
|
||||||
.context("Saving items")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
model::{SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace},
|
|
||||||
Db,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn default_workspace(
|
|
||||||
dock_pane: SerializedPane,
|
|
||||||
center_group: &SerializedPaneGroup,
|
|
||||||
) -> SerializedWorkspace {
|
|
||||||
SerializedWorkspace {
|
|
||||||
dock_anchor: crate::model::DockAnchor::Right,
|
|
||||||
dock_visible: false,
|
|
||||||
center_group: center_group.clone(),
|
|
||||||
dock_pane,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_dock_pane() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = Db::open_in_memory("basic_dock_pane");
|
|
||||||
|
|
||||||
let dock_pane = crate::model::SerializedPane {
|
|
||||||
children: vec![
|
|
||||||
SerializedItem::Terminal { item_id: 1 },
|
|
||||||
SerializedItem::Terminal { item_id: 4 },
|
|
||||||
SerializedItem::Terminal { item_id: 2 },
|
|
||||||
SerializedItem::Terminal { item_id: 3 },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let workspace = default_workspace(dock_pane, &Default::default());
|
|
||||||
|
|
||||||
db.save_workspace(&["/tmp"], None, &workspace);
|
|
||||||
|
|
||||||
let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(workspace.dock_pane, new_workspace.dock_pane);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simple_split() {
|
|
||||||
env_logger::try_init().ok();
|
|
||||||
|
|
||||||
let db = Db::open_in_memory("simple_split");
|
|
||||||
|
|
||||||
// -----------------
|
|
||||||
// | 1,2 | 5,6 |
|
|
||||||
// | - - - | |
|
|
||||||
// | 3,4 | |
|
|
||||||
// -----------------
|
|
||||||
let center_pane = SerializedPaneGroup::Group {
|
|
||||||
axis: crate::model::Axis::Horizontal,
|
|
||||||
children: vec![
|
|
||||||
SerializedPaneGroup::Group {
|
|
||||||
axis: crate::model::Axis::Vertical,
|
|
||||||
children: vec![
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane {
|
|
||||||
children: vec![
|
|
||||||
SerializedItem::Terminal { item_id: 1 },
|
|
||||||
SerializedItem::Terminal { item_id: 2 },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane {
|
|
||||||
children: vec![
|
|
||||||
SerializedItem::Terminal { item_id: 4 },
|
|
||||||
SerializedItem::Terminal { item_id: 3 },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
SerializedPaneGroup::Pane(SerializedPane {
|
|
||||||
children: vec![
|
|
||||||
SerializedItem::Terminal { item_id: 5 },
|
|
||||||
SerializedItem::Terminal { item_id: 6 },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
let workspace = default_workspace(Default::default(), ¢er_pane);
|
|
||||||
|
|
||||||
db.save_workspace(&["/tmp"], None, &workspace);
|
|
||||||
|
|
||||||
assert_eq!(workspace.center_group, center_pane);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -63,7 +63,7 @@ use std::{
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use util::{defer, post_inc, ResultExt, TryFutureExt as _};
|
use util::{defer, post_inc, ResultExt, TryFutureExt as _};
|
||||||
|
|
||||||
pub use db::Db;
|
pub use db::{kvp::KeyValue, Db};
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use worktree::*;
|
pub use worktree::*;
|
||||||
|
|
||||||
|
|
39
crates/sqlez/src/domain.rs
Normal file
39
crates/sqlez/src/domain.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::connection::Connection;
|
||||||
|
|
||||||
|
pub trait Domain: Send + Sync + Clone {
|
||||||
|
fn migrate(conn: &Connection) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D1: Domain, D2: Domain> Domain for (D1, D2) {
|
||||||
|
fn migrate(conn: &Connection) -> anyhow::Result<()> {
|
||||||
|
D1::migrate(conn)?;
|
||||||
|
D2::migrate(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D1: Domain, D2: Domain, D3: Domain> Domain for (D1, D2, D3) {
|
||||||
|
fn migrate(conn: &Connection) -> anyhow::Result<()> {
|
||||||
|
D1::migrate(conn)?;
|
||||||
|
D2::migrate(conn)?;
|
||||||
|
D3::migrate(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D1: Domain, D2: Domain, D3: Domain, D4: Domain> Domain for (D1, D2, D3, D4) {
|
||||||
|
fn migrate(conn: &Connection) -> anyhow::Result<()> {
|
||||||
|
D1::migrate(conn)?;
|
||||||
|
D2::migrate(conn)?;
|
||||||
|
D3::migrate(conn)?;
|
||||||
|
D4::migrate(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D1: Domain, D2: Domain, D3: Domain, D4: Domain, D5: Domain> Domain for (D1, D2, D3, D4, D5) {
|
||||||
|
fn migrate(conn: &Connection) -> anyhow::Result<()> {
|
||||||
|
D1::migrate(conn)?;
|
||||||
|
D2::migrate(conn)?;
|
||||||
|
D3::migrate(conn)?;
|
||||||
|
D4::migrate(conn)?;
|
||||||
|
D5::migrate(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod bindable;
|
pub mod bindable;
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
|
pub mod domain;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod savepoint;
|
pub mod savepoint;
|
||||||
pub mod statement;
|
pub mod statement;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indoc::{formatdoc, indoc};
|
use indoc::formatdoc;
|
||||||
|
|
||||||
use crate::connection::Connection;
|
use crate::connection::Connection;
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::{marker::PhantomData, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use connection::Connection;
|
use connection::Connection;
|
||||||
use thread_local::ThreadLocal;
|
use thread_local::ThreadLocal;
|
||||||
|
|
||||||
use crate::{connection, migrations::Migration};
|
use crate::{connection, domain::Domain};
|
||||||
|
|
||||||
pub struct ThreadSafeConnection {
|
pub struct ThreadSafeConnection<D: Domain> {
|
||||||
uri: Arc<str>,
|
uri: Arc<str>,
|
||||||
persistent: bool,
|
persistent: bool,
|
||||||
initialize_query: Option<&'static str>,
|
initialize_query: Option<&'static str>,
|
||||||
migrations: Option<&'static [Migration]>,
|
|
||||||
connection: Arc<ThreadLocal<Connection>>,
|
connection: Arc<ThreadLocal<Connection>>,
|
||||||
|
_pd: PhantomData<D>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThreadSafeConnection {
|
impl<D: Domain> ThreadSafeConnection<D> {
|
||||||
pub fn new(uri: &str, persistent: bool) -> Self {
|
pub fn new(uri: &str, persistent: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uri: Arc::from(uri),
|
uri: Arc::from(uri),
|
||||||
persistent,
|
persistent,
|
||||||
initialize_query: None,
|
initialize_query: None,
|
||||||
migrations: None,
|
|
||||||
connection: Default::default(),
|
connection: Default::default(),
|
||||||
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +31,6 @@ impl ThreadSafeConnection {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Migrations have to be run per connection because we fallback to memory
|
|
||||||
/// so this needs
|
|
||||||
pub fn with_migrations(mut self, migrations: &'static [Migration]) -> Self {
|
|
||||||
self.migrations = Some(migrations);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Opens a new db connection with the initialized file path. This is internal and only
|
/// Opens a new db connection with the initialized file path. This is internal and only
|
||||||
/// called from the deref function.
|
/// called from the deref function.
|
||||||
/// If opening fails, the connection falls back to a shared memory connection
|
/// If opening fails, the connection falls back to a shared memory connection
|
||||||
|
@ -50,21 +43,33 @@ impl ThreadSafeConnection {
|
||||||
fn open_shared_memory(&self) -> Connection {
|
fn open_shared_memory(&self) -> Connection {
|
||||||
Connection::open_memory(self.uri.as_ref())
|
Connection::open_memory(self.uri.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open a new connection for the given domain, leaving this
|
||||||
|
// connection intact.
|
||||||
|
pub fn for_domain<D2: Domain>(&self) -> ThreadSafeConnection<D2> {
|
||||||
|
ThreadSafeConnection {
|
||||||
|
uri: self.uri.clone(),
|
||||||
|
persistent: self.persistent,
|
||||||
|
initialize_query: self.initialize_query,
|
||||||
|
connection: Default::default(),
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for ThreadSafeConnection {
|
impl<D: Domain> Clone for ThreadSafeConnection<D> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uri: self.uri.clone(),
|
uri: self.uri.clone(),
|
||||||
persistent: self.persistent,
|
persistent: self.persistent,
|
||||||
initialize_query: self.initialize_query.clone(),
|
initialize_query: self.initialize_query.clone(),
|
||||||
migrations: self.migrations.clone(),
|
|
||||||
connection: self.connection.clone(),
|
connection: self.connection.clone(),
|
||||||
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ThreadSafeConnection {
|
impl<D: Domain> Deref for ThreadSafeConnection<D> {
|
||||||
type Target = Connection;
|
type Target = Connection;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -83,13 +88,7 @@ impl Deref for ThreadSafeConnection {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(migrations) = self.migrations {
|
D::migrate(&connection).expect("Migrations failed");
|
||||||
for migration in migrations {
|
|
||||||
migration
|
|
||||||
.run(&connection)
|
|
||||||
.expect(&format!("Migrations failed to execute: {:?}", migration));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection
|
connection
|
||||||
})
|
})
|
||||||
|
|
|
@ -1925,7 +1925,7 @@ mod tests {
|
||||||
|
|
||||||
let project = Project::test(fs, None, cx).await;
|
let project = Project::test(fs, None, cx).await;
|
||||||
let (_, workspace) =
|
let (_, workspace) =
|
||||||
cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx));
|
cx.add_window(|cx| Workspace::new(None, project, |_, _| unimplemented!(), cx));
|
||||||
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||||
|
|
||||||
add_labled_item(&workspace, &pane, "A", cx);
|
add_labled_item(&workspace, &pane, "A", cx);
|
||||||
|
|
|
@ -15,7 +15,7 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
|
use client::{proto, Client, PeerId, TypedEnvelope, UserStore};
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use db::{model::SerializedWorkspace, Db};
|
use db::{kvp::KeyValue, model::SerializedWorkspace, Db};
|
||||||
use dock::{DefaultItemFactory, Dock, ToggleDockButton};
|
use dock::{DefaultItemFactory, Dock, ToggleDockButton};
|
||||||
use drag_and_drop::DragAndDrop;
|
use drag_and_drop::DragAndDrop;
|
||||||
use fs::{self, Fs};
|
use fs::{self, Fs};
|
||||||
|
@ -1288,7 +1288,8 @@ impl Workspace {
|
||||||
|
|
||||||
// Use the resolved worktree roots to get the serialized_db from the database
|
// Use the resolved worktree roots to get the serialized_db from the database
|
||||||
let serialized_workspace = cx.read(|cx| {
|
let serialized_workspace = cx.read(|cx| {
|
||||||
cx.global::<Db>()
|
cx.global::<Db<KeyValue>>()
|
||||||
|
.open_as::<db::Workspace>()
|
||||||
.workspace_for_roots(&Vec::from_iter(worktree_roots.into_iter())[..])
|
.workspace_for_roots(&Vec::from_iter(worktree_roots.into_iter())[..])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn main() {
|
||||||
init_panic_hook(app_version, http.clone(), app.background());
|
init_panic_hook(app_version, http.clone(), app.background());
|
||||||
|
|
||||||
let db = app.background().spawn(async move {
|
let db = app.background().spawn(async move {
|
||||||
project::Db::open(&*zed::paths::DB_DIR, RELEASE_CHANNEL_NAME.as_str())
|
project::Db::<project::KeyValue>::open(&*zed::paths::DB_DIR, RELEASE_CHANNEL_NAME.as_str())
|
||||||
});
|
});
|
||||||
|
|
||||||
load_embedded_fonts(&app);
|
load_embedded_fonts(&app);
|
||||||
|
@ -150,7 +150,7 @@ fn main() {
|
||||||
let db = cx.background().block(db);
|
let db = cx.background().block(db);
|
||||||
cx.set_global(db);
|
cx.set_global(db);
|
||||||
|
|
||||||
client.start_telemetry(cx.global::<Db>().clone());
|
client.start_telemetry(cx.global::<Db<project::KeyValue>>().clone());
|
||||||
client.report_event("start app", Default::default());
|
client.report_event("start app", Default::default());
|
||||||
|
|
||||||
let app_state = Arc::new(AppState {
|
let app_state = Arc::new(AppState {
|
||||||
|
@ -165,7 +165,7 @@ fn main() {
|
||||||
default_item_factory,
|
default_item_factory,
|
||||||
});
|
});
|
||||||
auto_update::init(
|
auto_update::init(
|
||||||
cx.global::<Db>().clone(),
|
cx.global::<Db<project::KeyValue>>().clone(),
|
||||||
http,
|
http,
|
||||||
client::ZED_SERVER_URL.clone(),
|
client::ZED_SERVER_URL.clone(),
|
||||||
cx,
|
cx,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue