Skip to content

Commit 8c87348

Browse files
Improve graphman remove feedback (#6281)
* graph, store: return SubgraphNotFound error when removing non-existent subgraph Previously, remove_subgraph returned Ok(vecsilently when the subgraph didn't exist. Now it returns a proper StoreError::SubgraphNotFound error with the subgraph name, giving users clear feedback. Signed-off-by: Maksim Dimitrov <[email protected]> * node: validate remove command accepts only subgraph names Change the remove command to accept DeploymentSearch and validate that only subgraph names are accepted. Passing an IPFS hash or schema namespace now returns a clear error message instead of being silently treated as a name. Signed-off-by: Maksim Dimitrov <[email protected]> * server: propagate errors from remove_subgraph in graphman GraphQL resolver Add missing .map_err(GraphmanError::from) to properly convert store errors (including the new SubgraphNotFound) to GraphQL errors. Signed-off-by: Maksim Dimitrov <[email protected]> * test: verify SubgraphNotFound error for non-existent subgraph removal Signed-off-by: Maksim Dimitrov <[email protected]> * test: fix graphql_can_remove_subgraph test The test was silently passing because remove_subgraph returned Ok(vecfor non-existent subgraphs. Now that SubgraphNotFound error is properly returned, the test needs to actually create the subgraph first. Signed-off-by: Maksim Dimitrov <[email protected]> * test(integration): Ignore SubgraphNotFound errors when removing subgraphs that may not exist Signed-off-by: Maksim Dimitrov <[email protected]> * test(unit): Ignore SubgraphNotFound for missing subgraphs in cleanup Signed-off-by: Maksim Dimitrov <[email protected]> --------- Signed-off-by: Maksim Dimitrov <[email protected]>
1 parent fa20ecd commit 8c87348

File tree

9 files changed

+99
-20
lines changed

9 files changed

+99
-20
lines changed

graph/src/components/store/err.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub enum StoreError {
5656
ForkFailure(String),
5757
#[error("subgraph writer poisoned by previous error")]
5858
Poisoned,
59+
#[error("subgraph not found: {0}")]
60+
SubgraphNotFound(String),
5961
#[error("panic in subgraph writer: {0}")]
6062
WriterPanic(JoinError),
6163
#[error(
@@ -119,6 +121,7 @@ impl Clone for StoreError {
119121
Self::DatabaseUnavailable => Self::DatabaseUnavailable,
120122
Self::ForkFailure(arg0) => Self::ForkFailure(arg0.clone()),
121123
Self::Poisoned => Self::Poisoned,
124+
Self::SubgraphNotFound(arg0) => Self::SubgraphNotFound(arg0.clone()),
122125
Self::WriterPanic(arg0) => Self::Unknown(anyhow!("writer panic: {}", arg0)),
123126
Self::UnsupportedDeploymentSchemaVersion(arg0) => {
124127
Self::UnsupportedDeploymentSchemaVersion(*arg0)
@@ -202,6 +205,7 @@ impl StoreError {
202205
| Canceled
203206
| DatabaseUnavailable
204207
| ForkFailure(_)
208+
| SubgraphNotFound(_)
205209
| Poisoned
206210
| WriterPanic(_)
207211
| UnsupportedDeploymentSchemaVersion(_)

node/src/bin/manager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ pub enum Command {
165165
/// Remove a named subgraph
166166
Remove {
167167
/// The name of the subgraph to remove
168-
name: String,
168+
name: DeploymentSearch,
169169
},
170170
/// Create a subgraph name
171171
Create {
@@ -1745,7 +1745,7 @@ fn make_deployment_selector(
17451745
use graphman::deployment::DeploymentSelector::*;
17461746

17471747
match deployment {
1748-
DeploymentSearch::Name { name } => Name(name),
1748+
DeploymentSearch::Name { name } => Name(name.to_string()),
17491749
DeploymentSearch::Hash { hash, shard } => Subgraph { hash, shard },
17501750
DeploymentSearch::All => All,
17511751
DeploymentSearch::Deployment { namespace } => Schema(namespace),

node/src/manager/commands/remove.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,24 @@ use std::sync::Arc;
33
use graph::prelude::{anyhow, Error, SubgraphName, SubgraphStore as _};
44
use graph_store_postgres::SubgraphStore;
55

6-
pub async fn run(store: Arc<SubgraphStore>, name: &str) -> Result<(), Error> {
7-
let name = SubgraphName::new(name).map_err(|name| anyhow!("illegal subgraph name `{name}`"))?;
6+
use crate::manager::deployment::DeploymentSearch;
87

9-
println!("Removing subgraph {}", name);
10-
store.remove_subgraph(name).await?;
8+
pub async fn run(store: Arc<SubgraphStore>, name: &DeploymentSearch) -> Result<(), Error> {
9+
match name {
10+
DeploymentSearch::Name { name } => {
11+
let subgraph_name = SubgraphName::new(name)
12+
.map_err(|name| anyhow!("illegal subgraph name `{name}`"))?;
13+
println!("Removing subgraph {}", name);
14+
store.remove_subgraph(subgraph_name).await?;
15+
println!("Subgraph {} removed", name);
16+
}
17+
_ => {
18+
return Err(anyhow!(
19+
"Remove command expects a subgraph name, but got either hash or namespace: {}",
20+
name
21+
))
22+
}
23+
}
1124

1225
Ok(())
1326
}

server/graphman/src/resolvers/deployment_mutation/remove.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ pub async fn run(ctx: &GraphmanContext, name: &String) -> Result<()> {
2424
}
2525
};
2626

27-
let changes = catalog_conn.remove_subgraph(name).await?;
27+
let changes = catalog_conn
28+
.remove_subgraph(name)
29+
.await
30+
.map_err(GraphmanError::from)?;
2831
catalog_conn
2932
.send_store_event(&ctx.notification_sender, &StoreEvent::new(changes))
3033
.await?;

server/graphman/tests/deployment_mutation.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use graph::components::store::SubgraphStore;
66
use graph::prelude::DeploymentHash;
77
use serde::Deserialize;
88
use serde_json::json;
9-
use test_store::create_test_subgraph;
109
use test_store::SUBGRAPH_STORE;
10+
use test_store::{create_subgraph_name, create_test_subgraph};
1111
use tokio::time::sleep;
1212

1313
use self::util::client::send_graphql_request;
@@ -358,6 +358,8 @@ fn graphql_cannot_create_new_subgraph_with_invalid_name() {
358358
#[test]
359359
fn graphql_can_remove_subgraph() {
360360
run_test(|| async {
361+
create_subgraph_name("subgraph_1").await.unwrap();
362+
361363
let resp = send_graphql_request(
362364
json!({
363365
"query": r#"mutation RemoveSubgraph {
@@ -403,17 +405,44 @@ fn graphql_cannot_remove_subgraph_with_invalid_name() {
403405
)
404406
.await;
405407

406-
let success_resp = json!({
407-
"data": {
408-
"deployment": {
409-
"remove": {
410-
"success": true,
408+
let data = &resp["data"]["deployment"];
409+
let errors = resp["errors"].as_array().unwrap();
410+
411+
assert!(data.is_null());
412+
assert_eq!(errors.len(), 1);
413+
assert_eq!(
414+
errors[0]["message"].as_str().unwrap(),
415+
"store error: Subgraph name must contain only a-z, A-Z, 0-9, '-' and '_'"
416+
);
417+
});
418+
}
419+
420+
#[test]
421+
fn graphql_remove_returns_error_for_non_existing_subgraph() {
422+
run_test(|| async {
423+
let resp = send_graphql_request(
424+
json!({
425+
"query": r#"mutation RemoveNonExistingSubgraph {
426+
deployment {
427+
remove(name: "non_existing_subgraph") {
428+
success
429+
}
411430
}
412-
}
413-
}
414-
});
431+
}"#
432+
}),
433+
VALID_TOKEN,
434+
)
435+
.await;
415436

416-
assert_ne!(resp, success_resp);
437+
let data = &resp["data"]["deployment"];
438+
let errors = resp["errors"].as_array().unwrap();
439+
440+
assert!(data.is_null());
441+
assert_eq!(errors.len(), 1);
442+
assert_eq!(
443+
errors[0]["message"].as_str().unwrap(),
444+
"store error: subgraph not found: non_existing_subgraph"
445+
);
417446
});
418447
}
419448

store/postgres/src/primary.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ impl Connection {
11241124
.await?;
11251125
self.remove_unused_assignments().await
11261126
} else {
1127-
Ok(vec![])
1127+
Err(StoreError::SubgraphNotFound(name.to_string()))
11281128
}
11291129
}
11301130

store/test-store/src/store.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,19 @@ pub async fn create_test_subgraph_with_features(
260260
locator
261261
}
262262

263+
pub async fn create_subgraph_name(name: &str) -> Result<(), StoreError> {
264+
let subgraph_name = SubgraphName::new_unchecked(name.to_string());
265+
SUBGRAPH_STORE.create_subgraph(subgraph_name).await?;
266+
Ok(())
267+
}
268+
263269
pub async fn remove_subgraph(id: &DeploymentHash) {
264270
let name = SubgraphName::new_unchecked(id.to_string());
265-
SUBGRAPH_STORE.remove_subgraph(name).await.unwrap();
271+
// Ignore SubgraphNotFound errors during cleanup
272+
match SUBGRAPH_STORE.remove_subgraph(name).await {
273+
Ok(_) | Err(StoreError::SubgraphNotFound(_)) => {}
274+
Err(e) => panic!("unexpected error removing subgraph: {}", e),
275+
}
266276
let locs = SUBGRAPH_STORE.locators(id.as_str()).await.unwrap();
267277
let mut conn = primary_connection().await;
268278
for loc in locs {

store/test-store/tests/postgres/subgraph.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,3 +1232,18 @@ fn fail_unfail_non_deterministic_error_noop() {
12321232
test_store::remove_subgraphs().await;
12331233
})
12341234
}
1235+
1236+
#[test]
1237+
fn remove_nonexistent_subgraph_returns_not_found() {
1238+
test_store::run_test_sequentially(|store| async move {
1239+
remove_subgraphs().await;
1240+
1241+
let name = SubgraphName::new("nonexistent/subgraph").unwrap();
1242+
let result = store.subgraph_store().remove_subgraph(name.clone()).await;
1243+
1244+
assert!(matches!(
1245+
result,
1246+
Err(StoreError::SubgraphNotFound(n)) if n == name.to_string()
1247+
));
1248+
})
1249+
}

tests/src/fixture/mod.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,12 @@ pub async fn cleanup(
627627
hash: &DeploymentHash,
628628
) -> Result<(), Error> {
629629
let locators = subgraph_store.locators(hash).await?;
630-
subgraph_store.remove_subgraph(name.clone()).await?;
630+
// Remove subgraph if it exists, ignore not found errors
631+
match subgraph_store.remove_subgraph(name.clone()).await {
632+
Ok(_) | Err(graph::prelude::StoreError::SubgraphNotFound(_)) => {}
633+
Err(e) => return Err(e.into()),
634+
}
635+
631636
for locator in locators {
632637
subgraph_store.remove_deployment(locator.id.into()).await?;
633638
}

0 commit comments

Comments
 (0)