Skip to content

Commit 476bc56

Browse files
committed
Improve error messages for publication/subscription failures
Extract full PostgreSQL error details (code, message, detail, hint) instead of showing unhelpful "db error" messages. Helps users diagnose permission issues, missing tables, and other sync failures. Taariq Lewis, SerenAI, Paloma, and Volume at https://serendb.com
1 parent c781b4a commit 476bc56

5 files changed

Lines changed: 76 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [7.2.2] - 2026-01-05
6+
7+
### Improved
8+
9+
- **Better error messages for publication/subscription failures**: Publication and subscription creation errors now extract the full PostgreSQL error including error code, message, detail, and hint. Previously, errors would show unhelpful "db error" messages instead of the actual PostgreSQL error (e.g., missing privileges, table not found).
10+
511
## [7.2.1] - 2026-01-05
612

713
### Fixed

Cargo.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ members = [
77

88
[package]
99
name = "database-replicator"
10-
version = "7.2.1"
10+
version = "7.2.2"
1111
edition = "2021"
1212
license = "Apache-2.0"
1313
description = "Universal database-to-PostgreSQL replication CLI. Supports PostgreSQL, SQLite, MongoDB, and MySQL."

src/replication/publication.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,37 @@ pub async fn create_publication(
132132
execute_publication_query(client, publication_name, &query).await
133133
}
134134

135+
/// Extract detailed error message from tokio-postgres error
136+
fn extract_pg_error_details(e: &tokio_postgres::Error) -> String {
137+
let error_msg = e.to_string();
138+
139+
if let Some(db_error) = e.as_db_error() {
140+
// PostgreSQL returned a specific error - extract all details
141+
let mut details = format!(
142+
"PostgreSQL error {}: {}",
143+
db_error.code().code(),
144+
db_error.message()
145+
);
146+
if let Some(detail) = db_error.detail() {
147+
details.push_str(&format!("\nDetail: {}", detail));
148+
}
149+
if let Some(hint) = db_error.hint() {
150+
details.push_str(&format!("\nHint: {}", hint));
151+
}
152+
details
153+
} else if let Some(source) = std::error::Error::source(e) {
154+
// Check for underlying IO or TLS errors
155+
format!("{} (caused by: {})", error_msg, source)
156+
} else {
157+
// Use debug format for more information when to_string() is unhelpful
158+
if error_msg == "db error" || error_msg.len() < 20 {
159+
format!("{:?}", e)
160+
} else {
161+
error_msg
162+
}
163+
}
164+
}
165+
135166
async fn execute_publication_query(
136167
client: &Client,
137168
publication_name: &str,
@@ -143,7 +174,7 @@ async fn execute_publication_query(
143174
Ok(())
144175
}
145176
Err(e) => {
146-
let err_str = e.to_string();
177+
let err_str = extract_pg_error_details(&e);
147178
// Publication might already exist - that's okay
148179
if err_str.contains("already exists") {
149180
tracing::info!("✓ Publication '{}' already exists", publication_name);

src/replication/subscription.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@ use anyhow::{Context, Result};
55
use std::time::Duration;
66
use tokio_postgres::Client;
77

8+
/// Extract detailed error message from tokio-postgres error
9+
fn extract_pg_error_details(e: &tokio_postgres::Error) -> String {
10+
let error_msg = e.to_string();
11+
12+
if let Some(db_error) = e.as_db_error() {
13+
// PostgreSQL returned a specific error - extract all details
14+
let mut details = format!(
15+
"PostgreSQL error {}: {}",
16+
db_error.code().code(),
17+
db_error.message()
18+
);
19+
if let Some(detail) = db_error.detail() {
20+
details.push_str(&format!("\nDetail: {}", detail));
21+
}
22+
if let Some(hint) = db_error.hint() {
23+
details.push_str(&format!("\nHint: {}", hint));
24+
}
25+
details
26+
} else if let Some(source) = std::error::Error::source(e) {
27+
// Check for underlying IO or TLS errors
28+
format!("{} (caused by: {})", error_msg, source)
29+
} else {
30+
// Use debug format for more information when to_string() is unhelpful
31+
if error_msg == "db error" || error_msg.len() < 20 {
32+
format!("{:?}", e)
33+
} else {
34+
error_msg
35+
}
36+
}
37+
}
38+
839
/// Create a subscription to a publication on the source database
940
pub async fn create_subscription(
1041
client: &Client,
@@ -66,7 +97,7 @@ pub async fn create_subscription(
6697
Ok(())
6798
}
6899
Err(e) => {
69-
let err_str = e.to_string();
100+
let err_str = extract_pg_error_details(&e);
70101
// Subscription might already exist - that's okay
71102
if err_str.contains("already exists") {
72103
tracing::info!("✓ Subscription '{}' already exists", subscription_name);

0 commit comments

Comments
 (0)