Skip to content

Commit 5ec3f8f

Browse files
committed
server: support querying multiple subgraph features in single query
1 parent ad3cd9e commit 5ec3f8f

File tree

4 files changed

+92
-32
lines changed

4 files changed

+92
-32
lines changed

graph/src/data/subgraph/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ impl IntoValue for DeploymentFeatures {
597597
fn into_value(self) -> r::Value {
598598
object! {
599599
__typename: "SubgraphFeatures",
600+
subgraph: self.id,
600601
specVersion: self.spec_version,
601602
apiVersion: self.api_version,
602603
features: self.features,

server/index-node/src/resolver.rs

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::BTreeMap;
1+
use std::collections::{BTreeMap, HashSet};
22
use std::convert::TryInto;
33

44
use graph::data::query::Trace;
@@ -565,37 +565,54 @@ impl<S: Store> IndexNodeResolver<S> {
565565
field: &a::Field,
566566
) -> Result<r::Value, QueryExecutionError> {
567567
// We can safely unwrap because the argument is non-nullable and has been validated.
568-
let subgraph_id = field.get_required::<String>("subgraphId").unwrap();
568+
let subgraph_ids: HashSet<String> = field
569+
.get_required::<Vec<String>>("subgraphs")
570+
.unwrap()
571+
.into_iter()
572+
.collect();
569573

570-
// Try to build a deployment hash with the input string
571-
let deployment_hash = DeploymentHash::new(subgraph_id).map_err(|invalid_qm_hash| {
572-
QueryExecutionError::SubgraphDeploymentIdError(invalid_qm_hash)
573-
})?;
574+
if subgraph_ids.is_empty() {
575+
return Ok(r::Value::List(Vec::new()));
576+
}
574577

575578
let subgraph_store = self.store.subgraph_store();
576-
let features = match subgraph_store.subgraph_features(&deployment_hash).await? {
577-
Some(features) => {
578-
let mut deployment_features = features.clone();
579-
let features = &mut deployment_features.features;
579+
let mut all_features = vec![];
580580

581-
if deployment_features.has_declared_calls {
582-
features.push("declaredEthCalls".to_string());
583-
}
584-
if deployment_features.has_aggregations {
585-
features.push("aggregations".to_string());
581+
for subgraph_id in subgraph_ids {
582+
let deployment_hash = match DeploymentHash::new(subgraph_id) {
583+
Ok(hash) => hash,
584+
Err(_) => {
585+
continue;
586586
}
587-
if !deployment_features.immutable_entities.is_empty() {
588-
features.push("immutableEntities".to_string());
589-
}
590-
if deployment_features.has_bytes_as_ids {
591-
features.push("bytesAsIds".to_string());
587+
};
588+
589+
// Fetch features from store or IPFS
590+
let features = match subgraph_store.subgraph_features(&deployment_hash).await? {
591+
Some(features) => {
592+
let mut deployment_features = features.clone();
593+
let features = &mut deployment_features.features;
594+
595+
if deployment_features.has_declared_calls {
596+
features.push("declaredEthCalls".to_string());
597+
}
598+
if deployment_features.has_aggregations {
599+
features.push("aggregations".to_string());
600+
}
601+
if !deployment_features.immutable_entities.is_empty() {
602+
features.push("immutableEntities".to_string());
603+
}
604+
if deployment_features.has_bytes_as_ids {
605+
features.push("bytesAsIds".to_string());
606+
}
607+
deployment_features
592608
}
593-
deployment_features
594-
}
595-
None => self.get_features_from_ipfs(&deployment_hash).await?,
596-
};
609+
None => self.get_features_from_ipfs(&deployment_hash).await?,
610+
};
597611

598-
Ok(features.into_value())
612+
all_features.push(features.into_value());
613+
}
614+
615+
Ok(r::Value::List(all_features))
599616
}
600617

601618
fn resolve_api_versions(&self, _field: &a::Field) -> Result<r::Value, QueryExecutionError> {
@@ -817,6 +834,11 @@ impl<S: Store> Resolver for IndexNodeResolver<S> {
817834
self.resolve_public_proofs_of_indexing(field).await
818835
}
819836

837+
// The top-level `subgraphFeatures` field
838+
(None, "SubgraphFeatures", "subgraphFeatures") => {
839+
self.resolve_subgraph_features(field).await
840+
}
841+
820842
// Resolve fields of `Object` values (e.g. the `chains` field of `ChainIndexingStatus`)
821843
(value, _, _) => Ok(value.unwrap_or(r::Value::Null)),
822844
}
@@ -837,7 +859,6 @@ impl<S: Store> Resolver for IndexNodeResolver<S> {
837859
(None, "indexingStatusForPendingVersion") => {
838860
self.resolve_indexing_status_for_version(field, false)
839861
}
840-
(None, "subgraphFeatures") => self.resolve_subgraph_features(field).await,
841862
(None, "entityChangesInBlock") => self.resolve_entity_changes_in_block(field),
842863
// The top-level `subgraphVersions` field
843864
(None, "apiVersions") => self.resolve_api_versions(field),

server/index-node/src/schema.graphql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type Query {
3636
publicProofsOfIndexing(
3737
requests: [PublicProofOfIndexingRequest!]!
3838
): [PublicProofOfIndexingResult!]!
39-
subgraphFeatures(subgraphId: String!): SubgraphFeatures!
39+
subgraphFeatures(subgraphs: [String!]!): [SubgraphFeatures!]!
4040
entityChangesInBlock(subgraphId: String!, blockNumber: Int!): EntityChanges!
4141
blockData(network: String!, blockHash: Bytes!): JSONObject
4242
blockHashFromNumber(network: String!, blockNumber: Int!): Bytes
@@ -148,6 +148,7 @@ type CachedEthereumCall {
148148
}
149149

150150
type SubgraphFeatures {
151+
subgraph: String!
151152
apiVersion: String
152153
specVersion: String!
153154
features: [Feature!]!

tests/tests/integration_tests.rs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,8 @@ pub async fn test_block_handlers(ctx: TestContext) -> anyhow::Result<()> {
534534
// test subgraphFeatures endpoint returns handlers correctly
535535
let subgraph_features = Subgraph::query_with_vars(
536536
"query GetSubgraphFeatures($deployment: String!) {
537-
subgraphFeatures(subgraphId: $deployment) {
537+
subgraphFeatures(subgraphs: [$deployment]) {
538+
subgraph
538539
specVersion
539540
apiVersion
540541
features
@@ -546,7 +547,23 @@ pub async fn test_block_handlers(ctx: TestContext) -> anyhow::Result<()> {
546547
json!({ "deployment": subgraph.deployment }),
547548
)
548549
.await?;
549-
let handlers = &subgraph_features["data"]["subgraphFeatures"]["handlers"];
550+
// The response is now an array, get the first element
551+
let features_array = &subgraph_features["data"]["subgraphFeatures"];
552+
assert!(
553+
features_array.is_array(),
554+
"subgraphFeatures must return an array"
555+
);
556+
assert_eq!(
557+
features_array.as_array().unwrap().len(),
558+
1,
559+
"Expected exactly one subgraph feature set"
560+
);
561+
let subgraph_feature = &features_array[0];
562+
assert_eq!(
563+
subgraph_feature["subgraph"], subgraph.deployment,
564+
"Subgraph ID should match the deployment"
565+
);
566+
let handlers = &subgraph_feature["handlers"];
550567
assert!(
551568
handlers.is_array(),
552569
"subgraphFeatures.handlers must be an array"
@@ -762,7 +779,8 @@ async fn test_non_fatal_errors(ctx: TestContext) -> anyhow::Result<()> {
762779
assert!(!subgraph.healthy);
763780

764781
let query = "query GetSubgraphFeatures($deployment: String!) {
765-
subgraphFeatures(subgraphId: $deployment) {
782+
subgraphFeatures(subgraphs: [$deployment]) {
783+
subgraph
766784
specVersion
767785
apiVersion
768786
features
@@ -774,16 +792,35 @@ async fn test_non_fatal_errors(ctx: TestContext) -> anyhow::Result<()> {
774792

775793
let resp =
776794
Subgraph::query_with_vars(query, json!({ "deployment" : subgraph.deployment })).await?;
777-
let subgraph_features = &resp["data"]["subgraphFeatures"];
795+
796+
// The response is now an array, get the first element
797+
let features_array = &resp["data"]["subgraphFeatures"];
798+
assert!(
799+
features_array.is_array(),
800+
"subgraphFeatures must return an array"
801+
);
802+
assert_eq!(
803+
features_array.as_array().unwrap().len(),
804+
1,
805+
"Expected exactly one subgraph feature set"
806+
);
807+
808+
let subgraph_feature = &features_array[0];
809+
assert_eq!(
810+
subgraph_feature["subgraph"], subgraph.deployment,
811+
"Subgraph ID should match the deployment"
812+
);
813+
778814
let exp = json!({
815+
"subgraph": subgraph.deployment,
779816
"specVersion": "0.0.4",
780817
"apiVersion": "0.0.6",
781818
"features": ["nonFatalErrors"],
782819
"dataSources": ["ethereum/contract"],
783820
"handlers": ["block"],
784821
"network": "test",
785822
});
786-
assert_eq!(&exp, subgraph_features);
823+
assert_eq!(&exp, subgraph_feature);
787824

788825
let resp = subgraph
789826
.query("{ foos(orderBy: id, subgraphError: allow) { id } }")

0 commit comments

Comments
 (0)