Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions graph/src/data/subgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ impl IntoValue for DeploymentFeatures {
fn into_value(self) -> r::Value {
object! {
__typename: "SubgraphFeatures",
subgraph: self.id,
specVersion: self.spec_version,
apiVersion: self.api_version,
features: self.features,
Expand Down
73 changes: 47 additions & 26 deletions server/index-node/src/resolver.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashSet};
use std::convert::TryInto;

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

// Try to build a deployment hash with the input string
let deployment_hash = DeploymentHash::new(subgraph_id).map_err(|invalid_qm_hash| {
QueryExecutionError::SubgraphDeploymentIdError(invalid_qm_hash)
})?;
if subgraph_ids.is_empty() {
return Ok(r::Value::List(Vec::new()));
}

let subgraph_store = self.store.subgraph_store();
let features = match subgraph_store.subgraph_features(&deployment_hash).await? {
Some(features) => {
let mut deployment_features = features.clone();
let features = &mut deployment_features.features;
let mut all_features = vec![];

if deployment_features.has_declared_calls {
features.push("declaredEthCalls".to_string());
}
if deployment_features.has_aggregations {
features.push("aggregations".to_string());
for subgraph_id in subgraph_ids {
let deployment_hash = match DeploymentHash::new(subgraph_id) {
Ok(hash) => hash,
Err(_) => {
continue;
}
if !deployment_features.immutable_entities.is_empty() {
features.push("immutableEntities".to_string());
}
if deployment_features.has_bytes_as_ids {
features.push("bytesAsIds".to_string());
};

// Fetch features from store or IPFS
let features = match subgraph_store.subgraph_features(&deployment_hash).await? {
Some(features) => {
let mut deployment_features = features.clone();
let features = &mut deployment_features.features;

if deployment_features.has_declared_calls {
features.push("declaredEthCalls".to_string());
}
if deployment_features.has_aggregations {
features.push("aggregations".to_string());
}
if !deployment_features.immutable_entities.is_empty() {
features.push("immutableEntities".to_string());
}
if deployment_features.has_bytes_as_ids {
features.push("bytesAsIds".to_string());
}
deployment_features
}
deployment_features
}
None => self.get_features_from_ipfs(&deployment_hash).await?,
};
None => self.get_features_from_ipfs(&deployment_hash).await?,
};

Ok(features.into_value())
all_features.push(features.into_value());
}

Ok(r::Value::List(all_features))
}

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

// The top-level `subgraphFeatures` field
(None, "SubgraphFeatures", "subgraphFeatures") => {
self.resolve_subgraph_features(field).await
}

// Resolve fields of `Object` values (e.g. the `chains` field of `ChainIndexingStatus`)
(value, _, _) => Ok(value.unwrap_or(r::Value::Null)),
}
Expand All @@ -837,7 +859,6 @@ impl<S: Store> Resolver for IndexNodeResolver<S> {
(None, "indexingStatusForPendingVersion") => {
self.resolve_indexing_status_for_version(field, false)
}
(None, "subgraphFeatures") => self.resolve_subgraph_features(field).await,
(None, "entityChangesInBlock") => self.resolve_entity_changes_in_block(field),
// The top-level `subgraphVersions` field
(None, "apiVersions") => self.resolve_api_versions(field),
Expand Down
3 changes: 2 additions & 1 deletion server/index-node/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type Query {
publicProofsOfIndexing(
requests: [PublicProofOfIndexingRequest!]!
): [PublicProofOfIndexingResult!]!
subgraphFeatures(subgraphId: String!): SubgraphFeatures!
subgraphFeatures(subgraphs: [String!]!): [SubgraphFeatures!]!
entityChangesInBlock(subgraphId: String!, blockNumber: Int!): EntityChanges!
blockData(network: String!, blockHash: Bytes!): JSONObject
blockHashFromNumber(network: String!, blockNumber: Int!): Bytes
Expand Down Expand Up @@ -148,6 +148,7 @@ type CachedEthereumCall {
}

type SubgraphFeatures {
subgraph: String!
apiVersion: String
specVersion: String!
features: [Feature!]!
Expand Down
47 changes: 42 additions & 5 deletions tests/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,8 @@ pub async fn test_block_handlers(ctx: TestContext) -> anyhow::Result<()> {
// test subgraphFeatures endpoint returns handlers correctly
let subgraph_features = Subgraph::query_with_vars(
"query GetSubgraphFeatures($deployment: String!) {
subgraphFeatures(subgraphId: $deployment) {
subgraphFeatures(subgraphs: [$deployment]) {
subgraph
specVersion
apiVersion
features
Expand All @@ -546,7 +547,23 @@ pub async fn test_block_handlers(ctx: TestContext) -> anyhow::Result<()> {
json!({ "deployment": subgraph.deployment }),
)
.await?;
let handlers = &subgraph_features["data"]["subgraphFeatures"]["handlers"];
// The response is now an array, get the first element
let features_array = &subgraph_features["data"]["subgraphFeatures"];
assert!(
features_array.is_array(),
"subgraphFeatures must return an array"
);
assert_eq!(
features_array.as_array().unwrap().len(),
1,
"Expected exactly one subgraph feature set"
);
let subgraph_feature = &features_array[0];
assert_eq!(
subgraph_feature["subgraph"], subgraph.deployment,
"Subgraph ID should match the deployment"
);
let handlers = &subgraph_feature["handlers"];
assert!(
handlers.is_array(),
"subgraphFeatures.handlers must be an array"
Expand Down Expand Up @@ -762,7 +779,8 @@ async fn test_non_fatal_errors(ctx: TestContext) -> anyhow::Result<()> {
assert!(!subgraph.healthy);

let query = "query GetSubgraphFeatures($deployment: String!) {
subgraphFeatures(subgraphId: $deployment) {
subgraphFeatures(subgraphs: [$deployment]) {
subgraph
specVersion
apiVersion
features
Expand All @@ -774,16 +792,35 @@ async fn test_non_fatal_errors(ctx: TestContext) -> anyhow::Result<()> {

let resp =
Subgraph::query_with_vars(query, json!({ "deployment" : subgraph.deployment })).await?;
let subgraph_features = &resp["data"]["subgraphFeatures"];

// The response is now an array, get the first element
let features_array = &resp["data"]["subgraphFeatures"];
assert!(
features_array.is_array(),
"subgraphFeatures must return an array"
);
assert_eq!(
features_array.as_array().unwrap().len(),
1,
"Expected exactly one subgraph feature set"
);

let subgraph_feature = &features_array[0];
assert_eq!(
subgraph_feature["subgraph"], subgraph.deployment,
"Subgraph ID should match the deployment"
);

let exp = json!({
"subgraph": subgraph.deployment,
"specVersion": "0.0.4",
"apiVersion": "0.0.6",
"features": ["nonFatalErrors"],
"dataSources": ["ethereum/contract"],
"handlers": ["block"],
"network": "test",
});
assert_eq!(&exp, subgraph_features);
assert_eq!(&exp, subgraph_feature);

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