Skip to content

fix(workspace): gracefully handle failing db connections #194

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

Merged
Merged
14 changes: 7 additions & 7 deletions crates/pg_completions/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand Down Expand Up @@ -298,7 +298,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand Down Expand Up @@ -332,7 +332,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand All @@ -357,7 +357,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand Down Expand Up @@ -385,7 +385,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand All @@ -411,7 +411,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand All @@ -436,7 +436,7 @@ mod tests {
position: (position as u32).into(),
text,
tree: Some(&tree),
schema: &pg_schema_cache::SchemaCache::new(),
schema: &pg_schema_cache::SchemaCache::default(),
};

let ctx = CompletionContext::new(&params);
Expand Down
14 changes: 5 additions & 9 deletions crates/pg_configuration/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub struct DatabaseConfiguration {
/// The name of the database.
#[partial(bpaf(long("database")))]
pub database: String,

/// The connection timeout in seconds.
#[partial(bpaf(long("conn_timeout")))]
pub conn_timeout_secs: Option<u16>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but maybe we can set the default via bpaf?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If not, we should add it to the comments.

Copy link
Collaborator Author

@juleswritescode juleswritescode Feb 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I played around with using a bpaf fallback, but the partial macro requires that the fallback is wrapped in an Option.

If we want to use display_fallback, we would also need to implement Display for Option<u16>, but we can't because of the Orphan Rule.

A quick fix is to add the fallback to the comment, wdyt?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we can use the debug representation. Works for me, too.

Screenshot 2025-02-09 at 18 27 12

}

impl Default for DatabaseConfiguration {
Expand All @@ -36,15 +40,7 @@ impl Default for DatabaseConfiguration {
username: "postgres".to_string(),
password: "postgres".to_string(),
database: "postgres".to_string(),
conn_timeout_secs: Some(10),
}
}
}

impl DatabaseConfiguration {
pub fn to_connection_string(&self) -> String {
format!(
"postgres://{}:{}@{}:{}/{}",
self.username, self.password, self.host, self.port, self.database
)
}
}
1 change: 1 addition & 0 deletions crates/pg_configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl PartialConfiguration {
username: Some("postgres".to_string()),
password: Some("postgres".to_string()),
database: Some("postgres".to_string()),
conn_timeout_secs: Some(10),
}),
}
}
Expand Down
16 changes: 13 additions & 3 deletions crates/pg_lsp/src/handlers/completions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::session::Session;
use anyhow::Result;
use pg_workspace::workspace;
use pg_workspace::{workspace, WorkspaceError};
use tower_lsp::lsp_types::{self, CompletionItem, CompletionItemLabelDetails};

#[tracing::instrument(level = "trace", skip_all)]
Expand All @@ -26,12 +26,22 @@ pub fn get_completions(
pg_lsp_converters::negotiated_encoding(client_capabilities),
)?;

let completion_result = session
let completion_result = match session
.workspace
.get_completions(workspace::CompletionParams {
path,
position: offset,
})?;
}) {
Ok(result) => result,
Err(e) => match e {
WorkspaceError::DatabaseConnectionError(_) => {
return Ok(lsp_types::CompletionResponse::Array(vec![]));
}
_ => {
return Err(e.into());
}
},
};

let items: Vec<CompletionItem> = completion_result
.into_iter()
Expand Down
23 changes: 19 additions & 4 deletions crates/pg_schema_cache/src/schema_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::versions::Version;

#[derive(Debug, Clone, Default)]
pub struct SchemaCache {
cached_conn_str: String,

pub schemas: Vec<Schema>,
pub tables: Vec<Table>,
pub functions: Vec<Function>,
Expand All @@ -18,10 +20,6 @@ pub struct SchemaCache {
}

impl SchemaCache {
pub fn new() -> SchemaCache {
SchemaCache::default()
}

pub async fn load(pool: &PgPool) -> Result<SchemaCache, sqlx::Error> {
let (schemas, tables, functions, types, versions, columns) = futures_util::try_join!(
Schema::load(pool),
Expand All @@ -33,6 +31,7 @@ impl SchemaCache {
)?;

Ok(SchemaCache {
cached_conn_str: SchemaCache::pool_to_conn_str(pool),
schemas,
tables,
functions,
Expand All @@ -42,6 +41,22 @@ impl SchemaCache {
})
}

fn pool_to_conn_str(pool: &PgPool) -> String {
let conn = pool.connect_options();

format!(
"postgres://{}:<redacted_pw>@{}:{}/{}",
conn.get_username(),
conn.get_host(),
conn.get_port(),
conn.get_database().unwrap_or("default")
)
}

pub fn has_already_cached_connection(&self, pool: &PgPool) -> bool {
self.cached_conn_str == SchemaCache::pool_to_conn_str(pool)
}

/// Applies an AST node to the repository
///
/// For example, alter table add column will add the column to the table if it does not exist
Expand Down
16 changes: 7 additions & 9 deletions crates/pg_workspace/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
num::NonZeroU64,
path::{Path, PathBuf},
sync::{RwLock, RwLockReadGuard, RwLockWriteGuard},
time::Duration,
};

use ignore::gitignore::{Gitignore, GitignoreBuilder};
Expand Down Expand Up @@ -266,6 +267,7 @@ pub struct DatabaseSettings {
pub username: String,
pub password: String,
pub database: String,
pub conn_timeout_secs: Duration,
}

impl Default for DatabaseSettings {
Expand All @@ -276,19 +278,11 @@ impl Default for DatabaseSettings {
username: "postgres".to_string(),
password: "postgres".to_string(),
database: "postgres".to_string(),
conn_timeout_secs: Duration::from_secs(10),
}
}
}

impl DatabaseSettings {
pub fn to_connection_string(&self) -> String {
format!(
"postgres://{}:{}@{}:{}/{}",
self.username, self.password, self.host, self.port, self.database
)
}
}

impl From<PartialDatabaseConfiguration> for DatabaseSettings {
fn from(value: PartialDatabaseConfiguration) -> Self {
let d = DatabaseSettings::default();
Expand All @@ -298,6 +292,10 @@ impl From<PartialDatabaseConfiguration> for DatabaseSettings {
username: value.username.unwrap_or(d.username),
password: value.password.unwrap_or(d.password),
database: value.database.unwrap_or(d.database),
conn_timeout_secs: value
.conn_timeout_secs
.map(|s| Duration::from_secs(s.into()))
.unwrap_or(d.conn_timeout_secs),
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions crates/pg_workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@ pub trait Workspace: Send + Sync + RefUnwindSafe {
params: CompletionParams,
) -> Result<pg_completions::CompletionResult, WorkspaceError>;

/// Refresh the schema cache for this workspace
fn refresh_schema_cache(&self) -> Result<(), WorkspaceError>;

/// Update the global settings for this workspace
fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), WorkspaceError>;

Expand Down
4 changes: 0 additions & 4 deletions crates/pg_workspace/src/workspace/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ where
self.request("pglsp/get_file_content", params)
}

fn refresh_schema_cache(&self) -> Result<(), WorkspaceError> {
self.request("pglsp/refresh_schema_cache", ())
}

fn pull_diagnostics(
&self,
params: super::PullDiagnosticsParams,
Expand Down
Loading