diff --git a/.changesets/fix_query_hash_speedup.md b/.changesets/fix_query_hash_speedup.md new file mode 100644 index 0000000000..fe66cf05a3 --- /dev/null +++ b/.changesets/fix_query_hash_speedup.md @@ -0,0 +1,7 @@ +### Improve performance of query hashing by using a precomputed schema hash ([PR #6622](https://github.com/apollographql/router/pull/6622)) + +The router now uses a simpler and faster query hashing algorithm with more predictable CPU and memory usage. This improvement is enabled by using a precomputed hash of the entire schema, rather than computing and hashing the subset of types and fields used by each query. + +For more details on why these design decisions were made, please see the [PR description](https://github.com/apollographql/router/pull/6622) + +By [@IvanGoncharov](https://github.com/IvanGoncharov) in https://github.com/apollographql/router/pull/6622 \ No newline at end of file diff --git a/apollo-router/src/batching.rs b/apollo-router/src/batching.rs index 7e967275f2..10c65916a3 100644 --- a/apollo-router/src/batching.rs +++ b/apollo-router/src/batching.rs @@ -23,7 +23,6 @@ use crate::error::FetchError; use crate::error::SubgraphBatchingError; use crate::graphql; use crate::plugins::telemetry::otel::span_ext::OpenTelemetrySpanExt; -use crate::query_planner::fetch::QueryHash; use crate::services::http::HttpClientServiceFactory; use crate::services::process_batches; use crate::services::router; @@ -31,6 +30,7 @@ use crate::services::router::body::RouterBody; use crate::services::subgraph::SubgraphRequestId; use crate::services::SubgraphRequest; use crate::services::SubgraphResponse; +use crate::spec::QueryHash; use crate::Context; /// A query that is part of a batch. @@ -493,7 +493,6 @@ mod tests { use crate::graphql; use crate::graphql::Request; use crate::layers::ServiceExt as LayerExt; - use crate::query_planner::fetch::QueryHash; use crate::services::http::HttpClientServiceFactory; use crate::services::router; use crate::services::router::body; @@ -501,6 +500,7 @@ mod tests { use crate::services::subgraph::SubgraphRequestId; use crate::services::SubgraphRequest; use crate::services::SubgraphResponse; + use crate::spec::QueryHash; use crate::Configuration; use crate::Context; use crate::TestHarness; diff --git a/apollo-router/src/plugin/mod.rs b/apollo-router/src/plugin/mod.rs index dcf8c6c1ee..48bf71db3f 100644 --- a/apollo-router/src/plugin/mod.rs +++ b/apollo-router/src/plugin/mod.rs @@ -95,7 +95,7 @@ where Schema::parse_and_validate(supergraph_sdl.to_string(), PathBuf::from("synthetic")) .expect("failed to parse supergraph schema"), )) - .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into()) + .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into_inner()) .supergraph_sdl(supergraph_sdl) .notify(Notify::builder().build()) .build() @@ -119,7 +119,7 @@ where BoxError::from(e.errors.to_string()) })?, )) - .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into()) + .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into_inner()) .supergraph_sdl(supergraph_sdl) .notify(Notify::builder().build()) .build() @@ -136,7 +136,7 @@ where PluginInit::fake_builder() .config(config) - .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into()) + .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into_inner()) .supergraph_sdl(supergraph_sdl) .supergraph_schema(supergraph_schema) .launch_id(Arc::new("launch_id".to_string())) diff --git a/apollo-router/src/plugins/cache/entity.rs b/apollo-router/src/plugins/cache/entity.rs index d50531dde2..9b19703b0f 100644 --- a/apollo-router/src/plugins/cache/entity.rs +++ b/apollo-router/src/plugins/cache/entity.rs @@ -48,11 +48,11 @@ use crate::json_ext::PathElement; use crate::plugin::Plugin; use crate::plugin::PluginInit; use crate::plugins::authorization::CacheKeyMetadata; -use crate::query_planner::fetch::QueryHash; use crate::query_planner::OperationKind; use crate::services::subgraph; use crate::services::subgraph::SubgraphRequestId; use crate::services::supergraph; +use crate::spec::QueryHash; use crate::spec::TYPENAME; use crate::Context; use crate::Endpoint; @@ -1220,9 +1220,11 @@ pub(crate) fn hash_vary_headers(headers: &http::HeaderMap) -> String { hex::encode(digest.finalize().as_slice()) } +// XXX(@goto-bus-stop): this doesn't make much sense: QueryHash already includes the operation name. +// This function can be removed outright later at the cost of invalidating all entity caches. pub(crate) fn hash_query(query_hash: &QueryHash, body: &graphql::Request) -> String { let mut digest = Sha256::new(); - digest.update(&query_hash.0); + digest.update(query_hash.as_bytes()); digest.update(&[0u8; 1][..]); digest.update(body.operation_name.as_deref().unwrap_or("-").as_bytes()); digest.update(&[0u8; 1][..]); diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert-5.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert-5.snap index ed078770d9..8734f2dbc1 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert-5.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert-5.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:ab9056ba140750aa8fe58360172b450fa717e7ea177e4a3c9426fe1291a88da2:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:e734548bbe559d607f045e8fe5b5225543a1093dac991e137d1208a12af536de:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "cached", "cache_control": "public" }, { - "key": "version:1.0:subgraph:user:type:Query:hash:0d4d253b049bbea514a54a892902fa4b9b658aedc9b8f2a1308323cdeef3c0ca:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:user:type:Query:hash:3e689b15c9cee1f2812c319a66ddbe8bfc452c8ab6759566ed1987cae95178cd:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "cached", "cache_control": "public" } diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert.snap index a49a81580f..5f8ca305d8 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__insert.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:ab9056ba140750aa8fe58360172b450fa717e7ea177e4a3c9426fe1291a88da2:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:e734548bbe559d607f045e8fe5b5225543a1093dac991e137d1208a12af536de:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "new", "cache_control": "public" }, { - "key": "version:1.0:subgraph:user:type:Query:hash:0d4d253b049bbea514a54a892902fa4b9b658aedc9b8f2a1308323cdeef3c0ca:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:user:type:Query:hash:3e689b15c9cee1f2812c319a66ddbe8bfc452c8ab6759566ed1987cae95178cd:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "new", "cache_control": "public" } diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data-3.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data-3.snap index fbe291783c..dcb3aff923 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data-3.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data-3.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5221ff42b311b757445c096c023cee4fefab5de49735e421c494f1119326317b:hash:4913f52405bb614177e7c718d43da695c2f0e7411707c2f77f1c62380153c8d8:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5221ff42b311b757445c096c023cee4fefab5de49735e421c494f1119326317b:hash:5f460b74668616b2388b1afed7af6557ee4a0d60fbca18f3b4c59b8027035f32:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "cached", "cache_control": "[REDACTED]" }, { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:4913f52405bb614177e7c718d43da695c2f0e7411707c2f77f1c62380153c8d8:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:5f460b74668616b2388b1afed7af6557ee4a0d60fbca18f3b4c59b8027035f32:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "cached", "cache_control": "[REDACTED]" } diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data.snap index d32bd8453c..3f36da3436 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__no_data.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5221ff42b311b757445c096c023cee4fefab5de49735e421c494f1119326317b:hash:4913f52405bb614177e7c718d43da695c2f0e7411707c2f77f1c62380153c8d8:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5221ff42b311b757445c096c023cee4fefab5de49735e421c494f1119326317b:hash:5f460b74668616b2388b1afed7af6557ee4a0d60fbca18f3b4c59b8027035f32:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "new", "cache_control": "[REDACTED]" }, { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:4913f52405bb614177e7c718d43da695c2f0e7411707c2f77f1c62380153c8d8:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:5f460b74668616b2388b1afed7af6557ee4a0d60fbca18f3b4c59b8027035f32:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "new", "cache_control": "[REDACTED]" } diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-3.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-3.snap index 76fd27f7fa..0a2452b735 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-3.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-3.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:ab9056ba140750aa8fe58360172b450fa717e7ea177e4a3c9426fe1291a88da2:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:e734548bbe559d607f045e8fe5b5225543a1093dac991e137d1208a12af536de:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", "status": "cached", "cache_control": "private" }, { - "key": "version:1.0:subgraph:user:type:Query:hash:0d4d253b049bbea514a54a892902fa4b9b658aedc9b8f2a1308323cdeef3c0ca:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", + "key": "version:1.0:subgraph:user:type:Query:hash:3e689b15c9cee1f2812c319a66ddbe8bfc452c8ab6759566ed1987cae95178cd:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", "status": "cached", "cache_control": "private" } diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-5.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-5.snap index 76fd27f7fa..0a2452b735 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-5.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private-5.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:ab9056ba140750aa8fe58360172b450fa717e7ea177e4a3c9426fe1291a88da2:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:e734548bbe559d607f045e8fe5b5225543a1093dac991e137d1208a12af536de:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", "status": "cached", "cache_control": "private" }, { - "key": "version:1.0:subgraph:user:type:Query:hash:0d4d253b049bbea514a54a892902fa4b9b658aedc9b8f2a1308323cdeef3c0ca:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", + "key": "version:1.0:subgraph:user:type:Query:hash:3e689b15c9cee1f2812c319a66ddbe8bfc452c8ab6759566ed1987cae95178cd:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", "status": "cached", "cache_control": "private" } diff --git a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private.snap b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private.snap index 65da6044f9..ec2044d719 100644 --- a/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private.snap +++ b/apollo-router/src/plugins/cache/snapshots/apollo_router__plugins__cache__tests__private.snap @@ -1,15 +1,16 @@ --- source: apollo-router/src/plugins/cache/tests.rs expression: cache_keys +snapshot_kind: text --- [ { - "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:ab9056ba140750aa8fe58360172b450fa717e7ea177e4a3c9426fe1291a88da2:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", + "key": "version:1.0:subgraph:orga:type:Organization:entity:5811967f540d300d249ab30ae681359a7815fdb5d3dc71a94be1d491006a6b27:hash:e734548bbe559d607f045e8fe5b5225543a1093dac991e137d1208a12af536de:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c", "status": "new", "cache_control": "private" }, { - "key": "version:1.0:subgraph:user:type:Query:hash:0d4d253b049bbea514a54a892902fa4b9b658aedc9b8f2a1308323cdeef3c0ca:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", + "key": "version:1.0:subgraph:user:type:Query:hash:3e689b15c9cee1f2812c319a66ddbe8bfc452c8ab6759566ed1987cae95178cd:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c:03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4", "status": "new", "cache_control": "private" } diff --git a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap index de68830bfe..fd19a91eb7 100644 --- a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap +++ b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__non_overridden_field_yields_expected_query_plan.snap @@ -1,6 +1,7 @@ --- source: apollo-router/src/plugins/progressive_override/tests.rs expression: query_plan +snapshot_kind: text --- { "data": null, @@ -19,7 +20,7 @@ expression: query_plan "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "343157a7d5b7929ebdc0c17cbf0f23c8d3cf0c93a820856d3a189521cc2f24a2", + "schemaAwareHash": "83a61d8e05a2342e4c13c5b518e1e4a5b4cbc405477fce069c784526eb3b1ae5", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap index 2967a7d6f7..a12803669a 100644 --- a/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap +++ b/apollo-router/src/plugins/progressive_override/snapshots/apollo_router__plugins__progressive_override__tests__overridden_field_yields_expected_query_plan.snap @@ -1,6 +1,7 @@ --- source: apollo-router/src/plugins/progressive_override/tests.rs expression: query_plan +snapshot_kind: text --- { "data": { @@ -24,7 +25,7 @@ expression: query_plan "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "df2a0633d70ab97805722bae920647da51b7eb821b06d8a2499683c5c7024316", + "schemaAwareHash": "3c438d69bd20986101e4fb91167d5db4b871b250a5e5b670e19e9305b2756baa", "authorization": { "is_authenticated": false, "scopes": [], @@ -63,7 +64,7 @@ expression: query_plan "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "56ac7a7cc11b7f293acbdaf0327cb2b676415eab8343e9259322a1609c90455e", + "schemaAwareHash": "fcec2495ee805f82a5712f74f89797ed678eb5118ff8044a1c8fb232d6dd16df", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap index b093e7be08..cb80da3c18 100644 --- a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap +++ b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/src/plugins/expose_query_plan.rs expression: "serde_json::to_value(response).unwrap()" +snapshot_kind: text --- { "data": { @@ -69,7 +70,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "45b4beebcbf1df72ab950db7bd278417712b1aa39119317f44ad5b425bdb6997", + "schemaAwareHash": "5c4bde1b693a9d93618856d221a620783601d3e6141991ea1d49763dca5fe94b", "authorization": { "is_authenticated": false, "scopes": [], @@ -109,7 +110,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "645f3f8763133d2376e33ab3d1145be7ded0ccc8e94e20aba1fbaa34a51633da", + "schemaAwareHash": "1763ef26b5543dd364a96f6b29f9db6edbbe06ef4b260fd6dd59258cf09134b8", "authorization": { "is_authenticated": false, "scopes": [], @@ -156,7 +157,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "a79f69245d777abc4afbd7d0a8fc434137fa4fd1079ef082edf4c7746b5a0fcd", + "schemaAwareHash": "b634e94c76926292e24ea336046389758058cccf227b49917b625adccfc29d07", "authorization": { "is_authenticated": false, "scopes": [], @@ -200,7 +201,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "5ad94764f288a41312e07745510bf5dade2b63fb82c3d896f7d00408dbbe5cce", + "schemaAwareHash": "2ff7e653609dee610e4c5e06a666391889af36a0f78ce44a15cf758e4cc897e5", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap index b093e7be08..cb80da3c18 100644 --- a/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap +++ b/apollo-router/src/plugins/snapshots/apollo_router__plugins__expose_query_plan__tests__it_expose_query_plan.snap @@ -1,6 +1,7 @@ --- source: apollo-router/src/plugins/expose_query_plan.rs expression: "serde_json::to_value(response).unwrap()" +snapshot_kind: text --- { "data": { @@ -69,7 +70,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "45b4beebcbf1df72ab950db7bd278417712b1aa39119317f44ad5b425bdb6997", + "schemaAwareHash": "5c4bde1b693a9d93618856d221a620783601d3e6141991ea1d49763dca5fe94b", "authorization": { "is_authenticated": false, "scopes": [], @@ -109,7 +110,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "645f3f8763133d2376e33ab3d1145be7ded0ccc8e94e20aba1fbaa34a51633da", + "schemaAwareHash": "1763ef26b5543dd364a96f6b29f9db6edbbe06ef4b260fd6dd59258cf09134b8", "authorization": { "is_authenticated": false, "scopes": [], @@ -156,7 +157,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "a79f69245d777abc4afbd7d0a8fc434137fa4fd1079ef082edf4c7746b5a0fcd", + "schemaAwareHash": "b634e94c76926292e24ea336046389758058cccf227b49917b625adccfc29d07", "authorization": { "is_authenticated": false, "scopes": [], @@ -200,7 +201,7 @@ expression: "serde_json::to_value(response).unwrap()" "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "5ad94764f288a41312e07745510bf5dade2b63fb82c3d896f7d00408dbbe5cce", + "schemaAwareHash": "2ff7e653609dee610e4c5e06a666391889af36a0f78ce44a15cf758e4cc897e5", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/src/plugins/test.rs b/apollo-router/src/plugins/test.rs index 8b68888049..a59fd7c721 100644 --- a/apollo-router/src/plugins/test.rs +++ b/apollo-router/src/plugins/test.rs @@ -107,7 +107,7 @@ impl> + 'static> PluginTestHarness { let plugin_init = PluginInit::builder() .config(config_for_plugin.clone()) - .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into()) + .supergraph_schema_id(crate::spec::Schema::schema_id(&supergraph_sdl).into_inner()) .supergraph_sdl(supergraph_sdl) .supergraph_schema(Arc::new(parsed_schema)) .subgraph_schemas(Arc::new( diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 9be1d5c084..32dbe4605a 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -16,7 +16,6 @@ use tower::ServiceExt; use tower_service::Service; use tracing::Instrument; -use super::fetch::QueryHash; use crate::apollo_studio_interop::UsageReporting; use crate::cache::estimate_size; use crate::cache::storage::InMemoryCache; @@ -39,7 +38,9 @@ use crate::services::query_planner::PlanOptions; use crate::services::QueryPlannerContent; use crate::services::QueryPlannerRequest; use crate::services::QueryPlannerResponse; +use crate::spec::QueryHash; use crate::spec::Schema; +use crate::spec::SchemaHash; use crate::spec::SpecError; use crate::Configuration; @@ -49,6 +50,26 @@ pub(crate) type InMemoryCachePlanner = InMemoryCache>>; pub(crate) const APOLLO_OPERATION_ID: &str = "apollo::supergraph::operation_id"; +/// Hashed value of query planner configuration for use in cache keys. +#[derive(Clone, Hash, PartialEq, Eq)] +// XXX(@goto-bus-stop): I think this probably should not be pub(crate), but right now all fields in +// the cache keys are pub(crate), which I'm not going to change at this time :) +pub(crate) struct ConfigModeHash(Vec); + +impl std::fmt::Display for ConfigModeHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(&self.0)) + } +} + +impl std::fmt::Debug for ConfigModeHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ConfigModeHash") + .field(&hex::encode(&self.0)) + .finish() + } +} + /// A query planner wrapper that caches results. /// /// The query planner performs LRU caching. @@ -62,7 +83,7 @@ pub(crate) struct CachingQueryPlanner { subgraph_schemas: Arc, plugins: Arc, enable_authorization_directives: bool, - config_mode_hash: Arc, + config_mode_hash: Arc, } fn init_query_plan_from_redis( @@ -109,7 +130,7 @@ where let mut hasher = StructHasher::new(); configuration.rust_query_planner_config().hash(&mut hasher); - let config_mode_hash = Arc::new(QueryHash(hasher.finalize())); + let config_mode_hash = Arc::new(ConfigModeHash(hasher.finalize())); Ok(Self { cache, @@ -168,7 +189,7 @@ where hash, metadata, plan_options, - config_mode: _, + config_mode_hash: _, schema_id: _, }, _, @@ -178,7 +199,7 @@ where hash: Some(hash.clone()), metadata: metadata.clone(), plan_options: plan_options.clone(), - config_mode: self.config_mode_hash.clone(), + config_mode_hash: self.config_mode_hash.clone(), }, ) .take(count) @@ -223,7 +244,7 @@ where hash: None, metadata: CacheKeyMetadata::default(), plan_options: PlanOptions::default(), - config_mode: self.config_mode_hash.clone(), + config_mode_hash: self.config_mode_hash.clone(), }); } } @@ -239,7 +260,7 @@ where hash, metadata, plan_options, - config_mode: _, + config_mode_hash: _, } in all_cache_keys { let doc = match query_analysis @@ -254,10 +275,10 @@ where query: query.clone(), operation: operation_name.clone(), hash: doc.hash.clone(), - schema_id: Arc::clone(&self.schema.schema_id), + schema_id: self.schema.schema_id.clone(), metadata, plan_options, - config_mode: self.config_mode_hash.clone(), + config_mode_hash: self.config_mode_hash.clone(), }; if experimental_reuse_query_plans { @@ -441,10 +462,10 @@ where query: request.query.clone(), operation: request.operation_name.to_owned(), hash: doc.hash.clone(), - schema_id: Arc::clone(&self.schema.schema_id), + schema_id: self.schema.schema_id.clone(), metadata, plan_options, - config_mode: self.config_mode_hash.clone(), + config_mode_hash: self.config_mode_hash.clone(), }; let context = request.context.clone(); @@ -583,12 +604,15 @@ fn stats_report_key_hash(stats_report_key: &str) -> String { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct CachingQueryKey { pub(crate) query: String, - pub(crate) schema_id: Arc, pub(crate) operation: Option, pub(crate) hash: Arc, + // XXX(@goto-bus-stop): It's probably correct to remove this, since having it here is + // misleading. The schema ID is *not* used in the Redis cache, but it's okay because the QueryHash + // is schema-aware. + pub(crate) schema_id: SchemaHash, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, - pub(crate) config_mode: Arc, + pub(crate) config_mode_hash: Arc, } const ROUTER_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -605,7 +629,7 @@ impl std::fmt::Display for CachingQueryKey { "^plan_options".hash(&mut hasher); self.plan_options.hash(&mut hasher); "^config_mode".hash(&mut hasher); - self.config_mode.hash(&mut hasher); + self.config_mode_hash.hash(&mut hasher); let metadata = hex::encode(hasher.finalize()); write!( @@ -623,7 +647,7 @@ pub(crate) struct WarmUpCachingQueryKey { pub(crate) hash: Option>, pub(crate) metadata: CacheKeyMetadata, pub(crate) plan_options: PlanOptions, - pub(crate) config_mode: Arc, + pub(crate) config_mode_hash: Arc, } struct StructHasher { @@ -810,7 +834,7 @@ mod tests { referenced_fields_by_type: Default::default(), } .into(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), }; diff --git a/apollo-router/src/query_planner/fetch.rs b/apollo-router/src/query_planner/fetch.rs index 849c98b505..80922805eb 100644 --- a/apollo-router/src/query_planner/fetch.rs +++ b/apollo-router/src/query_planner/fetch.rs @@ -5,7 +5,6 @@ use apollo_compiler::ast; use apollo_compiler::collections::HashMap; use apollo_compiler::validation::Valid; use apollo_compiler::ExecutableDocument; -use apollo_compiler::Name; use indexmap::IndexSet; use serde::Deserialize; use serde::Serialize; @@ -36,8 +35,9 @@ use crate::plugins::authorization::CacheKeyMetadata; use crate::services::fetch::ErrorMapping; use crate::services::subgraph::BoxService; use crate::services::SubgraphRequest; -use crate::spec::query::change::QueryHashVisitor; +use crate::spec::QueryHash; use crate::spec::Schema; +use crate::spec::SchemaHash; /// GraphQL operation type. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Deserialize, Serialize)] @@ -100,7 +100,18 @@ pub(crate) type SubgraphSchemas = HashMap; pub(crate) struct SubgraphSchema { pub(crate) schema: Arc>, - pub(crate) implementers_map: HashMap, + // TODO: Ideally should have separate nominal type for subgraph's schema hash + pub(crate) hash: SchemaHash, +} + +impl SubgraphSchema { + pub(crate) fn new(schema: Valid) -> Self { + let sdl = schema.serialize().no_indent().to_string(); + Self { + schema: Arc::new(schema), + hash: SchemaHash::new(&sdl), + } + } } /// A fetch node. @@ -254,23 +265,6 @@ impl std::fmt::Display for SubgraphOperation { } } -#[derive(Clone, Default, Hash, PartialEq, Eq, Deserialize, Serialize)] -pub(crate) struct QueryHash(#[serde(with = "hex")] pub(crate) Vec); - -impl std::fmt::Debug for QueryHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("QueryHash") - .field(&hex::encode(&self.0)) - .finish() - } -} - -impl Display for QueryHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) - } -} - #[derive(Default)] pub(crate) struct Variables { pub(crate) variables: Object, @@ -588,20 +582,13 @@ impl FetchNode { pub(crate) fn init_parsed_operation_and_hash_subquery( &mut self, subgraph_schemas: &SubgraphSchemas, - supergraph_schema_hash: &str, ) -> Result<(), ValidationErrors> { let schema = &subgraph_schemas[self.service_name.as_ref()]; - let doc = self.operation.init_parsed(&schema.schema)?; - - if let Ok(hash) = QueryHashVisitor::hash_query( - &schema.schema, - supergraph_schema_hash, - &schema.implementers_map, - doc, + self.operation.init_parsed(&schema.schema)?; + self.schema_aware_hash = Arc::new(schema.hash.operation_hash( + self.operation.as_serialized(), self.operation_name.as_deref(), - ) { - self.schema_aware_hash = Arc::new(QueryHash(hash)); - } + )); Ok(()) } diff --git a/apollo-router/src/query_planner/plan.rs b/apollo-router/src/query_planner/plan.rs index 9b2b2f295e..c43b3d874b 100644 --- a/apollo-router/src/query_planner/plan.rs +++ b/apollo-router/src/query_planner/plan.rs @@ -19,11 +19,11 @@ use crate::json_ext::Object; use crate::json_ext::Path; use crate::json_ext::Value; use crate::plugins::authorization::CacheKeyMetadata; -use crate::query_planner::fetch::QueryHash; use crate::query_planner::fetch::SubgraphSchemas; use crate::services::query_planner::PlanOptions; use crate::spec::operation_limits::OperationLimits; use crate::spec::Query; +use crate::spec::QueryHash; /// A planner key. /// @@ -70,7 +70,7 @@ impl QueryPlan { .into(), root: Arc::new(root.unwrap_or_else(|| PlanNode::Sequence { nodes: Vec::new() })), formatted_query_plan: Default::default(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), } @@ -380,58 +380,39 @@ impl PlanNode { pub(crate) fn init_parsed_operations_and_hash_subqueries( &mut self, subgraph_schemas: &SubgraphSchemas, - supergraph_schema_hash: &str, ) -> Result<(), ValidationErrors> { match self { PlanNode::Fetch(fetch_node) => { - fetch_node.init_parsed_operation_and_hash_subquery( - subgraph_schemas, - supergraph_schema_hash, - )?; + fetch_node.init_parsed_operation_and_hash_subquery(subgraph_schemas)?; } PlanNode::Sequence { nodes } => { for node in nodes { - node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?; + node.init_parsed_operations_and_hash_subqueries(subgraph_schemas)?; } } PlanNode::Parallel { nodes } => { for node in nodes { - node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?; + node.init_parsed_operations_and_hash_subqueries(subgraph_schemas)?; } } - PlanNode::Flatten(flatten) => flatten.node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?, + PlanNode::Flatten(flatten) => flatten + .node + .init_parsed_operations_and_hash_subqueries(subgraph_schemas)?, PlanNode::Defer { primary, deferred } => { if let Some(node) = primary.node.as_mut() { - node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?; + node.init_parsed_operations_and_hash_subqueries(subgraph_schemas)?; } for deferred_node in deferred { if let Some(node) = &mut deferred_node.node { - Arc::make_mut(node).init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )? + Arc::make_mut(node) + .init_parsed_operations_and_hash_subqueries(subgraph_schemas)? } } } PlanNode::Subscription { primary: _, rest } => { if let Some(node) = rest.as_mut() { - node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?; + node.init_parsed_operations_and_hash_subqueries(subgraph_schemas)?; } } PlanNode::Condition { @@ -440,16 +421,10 @@ impl PlanNode { else_clause, } => { if let Some(node) = if_clause.as_mut() { - node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?; + node.init_parsed_operations_and_hash_subqueries(subgraph_schemas)?; } if let Some(node) = else_clause.as_mut() { - node.init_parsed_operations_and_hash_subqueries( - subgraph_schemas, - supergraph_schema_hash, - )?; + node.init_parsed_operations_and_hash_subqueries(subgraph_schemas)?; } } } diff --git a/apollo-router/src/query_planner/query_planner_service.rs b/apollo-router/src/query_planner/query_planner_service.rs index 66e0d3ec5e..68e57c62f6 100644 --- a/apollo-router/src/query_planner/query_planner_service.rs +++ b/apollo-router/src/query_planner/query_planner_service.rs @@ -39,7 +39,6 @@ use crate::plugins::authorization::UnauthorizedPaths; use crate::plugins::telemetry::config::ApolloSignatureNormalizationAlgorithm; use crate::plugins::telemetry::config::Conf as TelemetryConfig; use crate::query_planner::convert::convert_root_query_plan_node; -use crate::query_planner::fetch::QueryHash; use crate::query_planner::fetch::SubgraphSchema; use crate::query_planner::fetch::SubgraphSchemas; use crate::query_planner::labeler::add_defer_labels; @@ -50,7 +49,6 @@ use crate::services::QueryPlannerContent; use crate::services::QueryPlannerRequest; use crate::services::QueryPlannerResponse; use crate::spec::operation_limits::OperationLimits; -use crate::spec::query::change::QueryHashVisitor; use crate::spec::Query; use crate::spec::Schema; use crate::spec::SpecError; @@ -194,10 +192,7 @@ impl QueryPlannerService { .map(|(name, schema)| { ( name.to_string(), - SubgraphSchema { - implementers_map: schema.schema().implementers_map(), - schema: Arc::new(schema.schema().clone()), - }, + SubgraphSchema::new(schema.schema().clone()), ) }) .collect(), @@ -247,7 +242,7 @@ impl QueryPlannerService { )?; let (fragments, operation, defer_stats, schema_aware_hash) = - Query::extract_query_information(&self.schema, executable, operation_name)?; + Query::extract_query_information(&self.schema, &query, executable, operation_name)?; let subselections = crate::spec::query::subselections::collect_subselections( &self.configuration, @@ -285,10 +280,7 @@ impl QueryPlannerService { ) -> Result { let plan_result = self .plan_inner(doc, operation.clone(), plan_options, |root_node| { - root_node.init_parsed_operations_and_hash_subqueries( - &self.subgraph_schemas, - &self.schema.raw_sdl, - )?; + root_node.init_parsed_operations_and_hash_subqueries(&self.subgraph_schemas)?; root_node.extract_authorization_metadata(self.schema.supergraph_schema(), &key); Ok(()) }) @@ -386,19 +378,15 @@ impl Service for QueryPlannerService { .to_executable_validate(api_schema) // Assume transformation creates a valid document: ignore conversion errors .map_err(|e| SpecError::ValidationError(e.into()))?; - let hash = QueryHashVisitor::hash_query( - this.schema.supergraph_schema(), - &this.schema.raw_sdl, - &this.schema.implementers_map, - &executable_document, - operation_name.as_deref(), - ) - .map_err(|e| SpecError::QueryHashing(e.to_string()))?; + let hash = this + .schema + .schema_id + .operation_hash(&modified_query.to_string(), operation_name.as_deref()); doc = ParsedDocumentInner::new( modified_query, Arc::new(executable_document), operation_name.as_deref(), - Arc::new(QueryHash(hash)), + Arc::new(hash), )?; } } @@ -508,23 +496,21 @@ impl QueryPlannerService { }; if let Some((unauthorized_paths, new_doc)) = filter_res { - key.filtered_query = new_doc.to_string(); + let new_query = new_doc.to_string(); + let new_hash = self + .schema + .schema_id + .operation_hash(&new_query, key.operation_name.as_deref()); + + key.filtered_query = new_query; let executable_document = new_doc .to_executable_validate(self.schema.api_schema()) .map_err(|e| SpecError::ValidationError(e.into()))?; - let hash = QueryHashVisitor::hash_query( - self.schema.supergraph_schema(), - &self.schema.raw_sdl, - &self.schema.implementers_map, - &executable_document, - key.operation_name.as_deref(), - ) - .map_err(|e| SpecError::QueryHashing(e.to_string()))?; doc = ParsedDocumentInner::new( new_doc, Arc::new(executable_document), key.operation_name.as_deref(), - Arc::new(QueryHash(hash)), + Arc::new(new_hash), )?; selections.unauthorized.paths = unauthorized_paths; } diff --git a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__query_planner_service__tests__plan_root.snap b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__query_planner_service__tests__plan_root.snap index cbac25c490..65482760e0 100644 --- a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__query_planner_service__tests__plan_root.snap +++ b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__query_planner_service__tests__plan_root.snap @@ -1,6 +1,7 @@ --- -source: apollo-router/src/query_planner/bridge_query_planner.rs +source: apollo-router/src/query_planner/query_planner_service.rs expression: plan.root +snapshot_kind: text --- Fetch( FetchNode { @@ -15,7 +16,7 @@ Fetch( output_rewrites: None, context_rewrites: None, schema_aware_hash: QueryHash( - "16d9399fe60cb6dcc81f522fab6a54056e8b65b02095667b76f1cdd7048aab50", + "4105c3b46a60c2f68297c5f13c8455e4f22122f4e6d38f3c4d9e870cc07990e6", ), authorization: CacheKeyMetadata { is_authenticated: false, diff --git a/apollo-router/src/query_planner/tests.rs b/apollo-router/src/query_planner/tests.rs index 4494f442bf..babf395514 100644 --- a/apollo-router/src/query_planner/tests.rs +++ b/apollo-router/src/query_planner/tests.rs @@ -82,7 +82,7 @@ async fn mock_subgraph_service_withf_panics_should_be_reported_as_service_closed let query_plan: QueryPlan = QueryPlan { root: serde_json::from_str(test_query_plan!()).unwrap(), formatted_query_plan: Default::default(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), usage_reporting: UsageReporting { stats_report_key: "this is a test report key".to_string(), @@ -151,7 +151,7 @@ async fn fetch_includes_operation_name() { referenced_fields_by_type: Default::default(), } .into(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), }; @@ -219,7 +219,7 @@ async fn fetch_makes_post_requests() { referenced_fields_by_type: Default::default(), } .into(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), }; @@ -355,7 +355,7 @@ async fn defer() { stats_report_key: "this is a test report key".to_string(), referenced_fields_by_type: Default::default(), }.into(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), }; @@ -655,7 +655,7 @@ async fn dependent_mutations() { referenced_fields_by_type: Default::default(), } .into(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), }; @@ -1877,7 +1877,7 @@ fn broken_plan_does_not_panic() { referenced_fields_by_type: Default::default(), } .into(), - query: Arc::new(Query::empty()), + query: Arc::new(Query::empty_for_tests()), query_metrics: Default::default(), estimated_size: Default::default(), }; @@ -1885,13 +1885,11 @@ fn broken_plan_does_not_panic() { let mut subgraph_schemas = HashMap::default(); subgraph_schemas.insert( "X".to_owned(), - query_planner::fetch::SubgraphSchema { - implementers_map: subgraph_schema.implementers_map(), - schema: Arc::new(subgraph_schema), - }, + query_planner::fetch::SubgraphSchema::new(subgraph_schema), ); - let result = Arc::make_mut(&mut plan.root) - .init_parsed_operations_and_hash_subqueries(&subgraph_schemas, ""); + // Run the plan initialization code to make sure it doesn't panic. + let result = + Arc::make_mut(&mut plan.root).init_parsed_operations_and_hash_subqueries(&subgraph_schemas); assert_eq!( result.unwrap_err().to_string(), r#"[1:3] Cannot query field "invalid" on type "Query"."# diff --git a/apollo-router/src/router_factory.rs b/apollo-router/src/router_factory.rs index b3cfebdba7..54c2c85922 100644 --- a/apollo-router/src/router_factory.rs +++ b/apollo-router/src/router_factory.rs @@ -160,13 +160,13 @@ impl RouterSuperServiceFactory for YamlRouterFactory { .get("telemetry") .cloned(); if let Some(plugin_config) = &mut telemetry_config { - inject_schema_id(Some(&schema.schema_id), plugin_config); + inject_schema_id(schema.schema_id.as_str(), plugin_config); match factory .create_instance( PluginInit::builder() .config(plugin_config.clone()) .supergraph_sdl(schema.raw_sdl.clone()) - .supergraph_schema_id(schema.schema_id.clone()) + .supergraph_schema_id(schema.schema_id.clone().into_inner()) .supergraph_schema(Arc::new(schema.supergraph_schema().clone())) .notify(configuration.notify.clone()) .build(), @@ -555,7 +555,7 @@ pub(crate) async fn create_plugins( extra_plugins: Option)>>, ) -> Result { let supergraph_schema = Arc::new(schema.supergraph_schema().clone()); - let supergraph_schema_id = schema.schema_id.clone(); + let supergraph_schema_id = schema.schema_id.clone().into_inner(); let mut apollo_plugins_config = configuration.apollo_plugins.clone().plugins; let user_plugins_config = configuration.plugins.clone().plugins.unwrap_or_default(); let extra = extra_plugins.unwrap_or_default(); @@ -612,10 +612,7 @@ pub(crate) async fn create_plugins( // give it some. If any of the other mandatory plugins need special // treatment, then we'll have to perform it here. // This is *required* by the telemetry module or it will fail... - inject_schema_id( - Some(&Schema::schema_id(&schema.raw_sdl)), - &mut plugin_config, - ); + inject_schema_id(&supergraph_schema_id, &mut plugin_config); } add_plugin!(name.to_string(), factory, plugin_config); } @@ -736,7 +733,11 @@ pub(crate) async fn create_plugins( } } -fn inject_schema_id(schema_id: Option<&str>, configuration: &mut Value) { +fn inject_schema_id( + // Ideally we'd use &SchemaHash, but we'll need to update a bunch of tests to do so + schema_id: &str, + configuration: &mut Value, +) { if configuration.get("apollo").is_none() { // Warning: this must be done here, otherwise studio reporting will not work if apollo_key().is_some() && apollo_graph_reference().is_some() { @@ -747,7 +748,7 @@ fn inject_schema_id(schema_id: Option<&str>, configuration: &mut Value) { return; } } - if let (Some(schema_id), Some(apollo)) = (schema_id, configuration.get_mut("apollo")) { + if let Some(apollo) = configuration.get_mut("apollo") { if let Some(apollo) = apollo.as_object_mut() { apollo.insert( "schema_id".to_string(), @@ -892,7 +893,7 @@ mod test { fn test_inject_schema_id() { let mut config = json!({ "apollo": {} }); inject_schema_id( - Some("8e2021d131b23684671c3b85f82dfca836908c6a541bbd5c3772c66e7f8429d8"), + "8e2021d131b23684671c3b85f82dfca836908c6a541bbd5c3772c66e7f8429d8", &mut config, ); let config = diff --git a/apollo-router/src/services/layers/query_analysis.rs b/apollo-router/src/services/layers/query_analysis.rs index c5e3c7b54c..12e3694afa 100644 --- a/apollo-router/src/services/layers/query_analysis.rs +++ b/apollo-router/src/services/layers/query_analysis.rs @@ -26,11 +26,11 @@ use crate::plugins::authorization::AuthorizationPlugin; use crate::plugins::telemetry::config::ApolloMetricsReferenceMode; use crate::plugins::telemetry::config::Conf as TelemetryConfig; use crate::plugins::telemetry::consts::QUERY_PARSING_SPAN_NAME; -use crate::query_planner::fetch::QueryHash; use crate::query_planner::OperationKind; use crate::services::SupergraphRequest; use crate::services::SupergraphResponse; use crate::spec::Query; +use crate::spec::QueryHash; use crate::spec::Schema; use crate::spec::SpecError; use crate::Configuration; @@ -313,7 +313,7 @@ impl Display for ParsedDocumentInner { impl Hash for ParsedDocumentInner { fn hash(&self, state: &mut H) { - self.hash.0.hash(state); + self.hash.hash(state); } } diff --git a/apollo-router/src/services/subgraph.rs b/apollo-router/src/services/subgraph.rs index ad3e431c9e..1c671eb13f 100644 --- a/apollo-router/src/services/subgraph.rs +++ b/apollo-router/src/services/subgraph.rs @@ -31,7 +31,7 @@ use crate::json_ext::Path; use crate::plugins::authentication::APOLLO_AUTHENTICATION_JWT_CLAIMS; use crate::plugins::authorization::CacheKeyMetadata; use crate::query_planner::fetch::OperationKind; -use crate::query_planner::fetch::QueryHash; +use crate::spec::QueryHash; use crate::Context; pub type BoxService = tower::util::BoxService; @@ -102,7 +102,10 @@ impl Request { subgraph_name, subscription_stream, connection_closed_signal, - query_hash: Default::default(), + // It's NOT GREAT! to have an empty hash value here. + // This value is populated based on the subgraph query hash in the query planner code. + // At the time of writing it's in `crate::query_planner::fetch::FetchNode::fetch_node`. + query_hash: QueryHash::default().into(), authorization: Default::default(), executable_document: None, id: SubgraphRequestId::new(), diff --git a/apollo-router/src/spec/mod.rs b/apollo-router/src/spec/mod.rs index 786eb8a056..948751b8a8 100644 --- a/apollo-router/src/spec/mod.rs +++ b/apollo-router/src/spec/mod.rs @@ -14,7 +14,9 @@ pub(crate) use field_type::*; pub(crate) use fragments::*; pub(crate) use query::Query; pub(crate) use query::TYPENAME; +pub(crate) use schema::QueryHash; pub(crate) use schema::Schema; +pub(crate) use schema::SchemaHash; pub(crate) use selection::*; use serde::Deserialize; use serde::Serialize; diff --git a/apollo-router/src/spec/query.rs b/apollo-router/src/spec/query.rs index 6ddf3ac1df..2bdba4e9a0 100644 --- a/apollo-router/src/spec/query.rs +++ b/apollo-router/src/spec/query.rs @@ -17,11 +17,11 @@ use serde::Serialize; use serde_json_bytes::ByteString; use tracing::level_filters::LevelFilter; -use self::change::QueryHashVisitor; use self::subselections::BooleanValues; use self::subselections::SubSelectionKey; use self::subselections::SubSelectionValue; use super::Fragment; +use super::QueryHash; use crate::error::FetchError; use crate::graphql::Error; use crate::graphql::Request; @@ -32,7 +32,6 @@ use crate::json_ext::ResponsePathElement; use crate::json_ext::Value; use crate::plugins::authorization::UnauthorizedPaths; use crate::query_planner::fetch::OperationKind; -use crate::query_planner::fetch::QueryHash; use crate::services::layers::query_analysis::get_operation; use crate::services::layers::query_analysis::ParsedDocument; use crate::services::layers::query_analysis::ParsedDocumentInner; @@ -45,7 +44,6 @@ use crate::spec::Selection; use crate::spec::SpecError; use crate::Configuration; -pub(crate) mod change; pub(crate) mod subselections; pub(crate) mod transform; pub(crate) mod traverse; @@ -74,12 +72,9 @@ pub(crate) struct Query { /// This is a hash that depends on: /// - the query itself - /// - the relevant parts of the schema - /// - /// if a schema update does not affect a query, then this will be the same hash - /// with the old and new schema + /// - the schema #[derivative(PartialEq = "ignore", Hash = "ignore")] - pub(crate) schema_aware_hash: Vec, + pub(crate) schema_aware_hash: QueryHash, } #[derive(Debug, Serialize, Deserialize)] @@ -95,7 +90,11 @@ pub(crate) struct DeferStats { } impl Query { - pub(crate) fn empty() -> Self { + /// Returns an empty query. This should be used somewhat carefully and only in tests. + /// Other parts of the router may not handle empty queries properly. + /// + /// FIXME: This should be marked cfg(test) but it's used in places where adding cfg(test) is tricky. + pub(crate) fn empty_for_tests() -> Self { Self { string: String::new(), fragments: Fragments { @@ -111,7 +110,7 @@ impl Query { conditional_defer_variable_names: IndexSet::default(), }, is_original: true, - schema_aware_hash: vec![], + schema_aware_hash: QueryHash::default(), } } @@ -266,38 +265,30 @@ impl Query { let recursion_limit = parser.recursion_reached(); tracing::trace!(?recursion_limit, "recursion limit data"); - let hash = QueryHashVisitor::hash_query( - schema.supergraph_schema(), - &schema.raw_sdl, - &schema.implementers_map, - &executable_document, - operation_name, - ) - .map_err(|e| SpecError::QueryHashing(e.to_string()))?; - + let hash = schema.schema_id.operation_hash(query, operation_name); ParsedDocumentInner::new( ast, Arc::new(executable_document), operation_name, - Arc::new(QueryHash(hash)), + Arc::new(hash), ) } #[cfg(test)] pub(crate) fn parse( - query: impl Into, + query_text: impl Into, operation_name: Option<&str>, schema: &Schema, configuration: &Configuration, ) -> Result { - let query = query.into(); + let query_text = query_text.into(); - let doc = Self::parse_document(&query, operation_name, schema, configuration)?; + let doc = Self::parse_document(&query_text, operation_name, schema, configuration)?; let (fragments, operation, defer_stats, schema_aware_hash) = - Self::extract_query_information(schema, &doc.executable, operation_name)?; + Self::extract_query_information(schema, &query_text, &doc.executable, operation_name)?; Ok(Query { - string: query, + string: query_text, fragments, operation, subselections: HashMap::new(), @@ -312,9 +303,10 @@ impl Query { /// Extract serializable data structures from the apollo-compiler HIR. pub(crate) fn extract_query_information( schema: &Schema, + query_text: &str, document: &ExecutableDocument, operation_name: Option<&str>, - ) -> Result<(Fragments, Operation, DeferStats, Vec), SpecError> { + ) -> Result<(Fragments, Operation, DeferStats, QueryHash), SpecError> { let mut defer_stats = DeferStats { has_defer: false, has_unconditional_defer: false, @@ -323,18 +315,7 @@ impl Query { let fragments = Fragments::from_hir(document, schema, &mut defer_stats)?; let operation = get_operation(document, operation_name)?; let operation = Operation::from_hir(&operation, schema, &mut defer_stats, &fragments)?; - - let mut visitor = QueryHashVisitor::new( - schema.supergraph_schema(), - &schema.raw_sdl, - &schema.implementers_map, - document, - ) - .map_err(|e| SpecError::QueryHashing(format!("could not calculate the query hash: {e}")))?; - traverse::document(&mut visitor, document, operation_name).map_err(|e| { - SpecError::QueryHashing(format!("could not calculate the query hash: {e}")) - })?; - let hash = visitor.finish(); + let hash = schema.schema_id.operation_hash(query_text, operation_name); Ok((fragments, operation, defer_stats, hash)) } diff --git a/apollo-router/src/spec/query/change.rs b/apollo-router/src/spec/query/change.rs deleted file mode 100644 index e8da0caa39..0000000000 --- a/apollo-router/src/spec/query/change.rs +++ /dev/null @@ -1,2667 +0,0 @@ -//! Schema aware query hashing algorithm -//! -//! This is a query visitor that calculates a hash of all fields, along with all -//! the relevant types and directives in the schema. It is designed to generate -//! the same hash for the same query across schema updates if the schema change -//! would not affect that query. As an example, if a new type is added to the -//! schema, we know that it will have no impact to an existing query that cannot -//! be using it. -//! This algorithm is used in 2 places: -//! * in the query planner cache: generating query plans can be expensive, so the -//! router has a warm up feature, where upon receving a new schema, it will take -//! the most used queries and plan them, before switching traffic to the new -//! schema. Generating all of those plans takes a lot of time. By using this -//! hashing algorithm, we can detect that the schema change does not affect the -//! query, which means that we can reuse the old query plan directly and avoid -//! the expensive planning task -//! * in entity caching: the responses returned by subgraphs can change depending -//! on the schema (example: a field moving from String to Int), so we need to -//! detect that. One way to do it was to add the schema hash to the cache key, but -//! as a result it wipes the cache on every schema update, which will cause -//! performance and reliability issues. With this hashing algorithm, cached entries -//! can be kept across schema updates -//! -//! ## Technical details -//! -//! ### Query string hashing -//! A full hash of the query string is added along with the schema level data. This -//! is technically making the algorithm less useful, because the same query with -//! different indentation would get a different hash, while there would be no difference -//! in the query plan or the subgraph response. But this makes sure that if we forget -//! something in the way we hash the query, we will avoid collisions. -//! -//! ### Prefixes and suffixes -//! Across the entire visitor, we add prefixes and suffixes like this: -//! -//! ```rust -//! "^SCHEMA".hash(self); -//! ``` -//! -//! This prevents possible collision while hashing multiple things in a sequence. The -//! `^` character cannot be present in GraphQL names, so this is a good separator. -use std::hash::Hash; -use std::hash::Hasher; - -use apollo_compiler::ast; -use apollo_compiler::ast::FieldDefinition; -use apollo_compiler::collections::HashMap; -use apollo_compiler::collections::HashSet; -use apollo_compiler::executable; -use apollo_compiler::parser::Parser; -use apollo_compiler::schema; -use apollo_compiler::schema::DirectiveList; -use apollo_compiler::schema::ExtendedType; -use apollo_compiler::schema::InterfaceType; -use apollo_compiler::validation::Valid; -use apollo_compiler::Name; -use apollo_compiler::Node; -use sha2::Digest; -use sha2::Sha256; -use tower::BoxError; - -use super::traverse; -use super::traverse::Visitor; -use crate::plugins::progressive_override::JOIN_FIELD_DIRECTIVE_NAME; -use crate::plugins::progressive_override::JOIN_SPEC_BASE_URL; -use crate::spec::Schema; - -pub(crate) const JOIN_TYPE_DIRECTIVE_NAME: &str = "join__type"; -pub(crate) const CONTEXT_SPEC_BASE_URL: &str = "https://specs.apollo.dev/context"; -pub(crate) const CONTEXT_DIRECTIVE_NAME: &str = "context"; - -/// Calculates a hash of the query and the schema, but only looking at the parts of the -/// schema which affect the query. -/// This means that if a schema update does not affect an existing query (example: adding a field), -/// then the hash will stay the same -pub(crate) struct QueryHashVisitor<'a> { - schema: &'a schema::Schema, - // TODO: remove once introspection has been moved out of query planning - // For now, introspection is still handled by the planner, so when an - // introspection query is hashed, it should take the whole schema into account - schema_str: &'a str, - implementers_map: &'a HashMap, - hasher: Sha256, - fragments: HashMap<&'a Name, &'a Node>, - hashed_types: HashSet, - hashed_field_definitions: HashSet<(String, String)>, - seen_introspection: bool, - join_field_directive_name: Option, - join_type_directive_name: Option, - context_directive_name: Option, - // map from context string to list of type names - contexts: HashMap>, -} - -impl<'a> QueryHashVisitor<'a> { - pub(crate) fn new( - schema: &'a schema::Schema, - schema_str: &'a str, - implementers_map: &'a HashMap, - executable: &'a executable::ExecutableDocument, - ) -> Result { - let mut visitor = Self { - schema, - schema_str, - implementers_map, - hasher: Sha256::new(), - fragments: executable.fragments.iter().collect(), - hashed_types: HashSet::default(), - hashed_field_definitions: HashSet::default(), - seen_introspection: false, - // should we just return an error if we do not find those directives? - join_field_directive_name: Schema::directive_name( - schema, - JOIN_SPEC_BASE_URL, - ">=0.1.0", - JOIN_FIELD_DIRECTIVE_NAME, - ), - join_type_directive_name: Schema::directive_name( - schema, - JOIN_SPEC_BASE_URL, - ">=0.1.0", - JOIN_TYPE_DIRECTIVE_NAME, - ), - context_directive_name: Schema::directive_name( - schema, - CONTEXT_SPEC_BASE_URL, - ">=0.1.0", - CONTEXT_DIRECTIVE_NAME, - ), - contexts: HashMap::default(), - }; - - visitor.hash_schema()?; - - Ok(visitor) - } - - pub(crate) fn hash_schema(&mut self) -> Result<(), BoxError> { - "^SCHEMA".hash(self); - for directive_definition in self.schema.directive_definitions.values() { - self.hash_directive_definition(directive_definition)?; - } - - self.hash_directive_list_schema(&self.schema.schema_definition.directives); - - "^SCHEMA-END".hash(self); - Ok(()) - } - - pub(crate) fn hash_query( - schema: &'a schema::Schema, - schema_str: &'a str, - implementers_map: &'a HashMap, - executable: &'a executable::ExecutableDocument, - operation_name: Option<&str>, - ) -> Result, BoxError> { - let mut visitor = QueryHashVisitor::new(schema, schema_str, implementers_map, executable)?; - traverse::document(&mut visitor, executable, operation_name)?; - // hash the entire query string to prevent collisions - executable.to_string().hash(&mut visitor); - Ok(visitor.finish()) - } - - pub(crate) fn finish(self) -> Vec { - self.hasher.finalize().as_slice().into() - } - - fn hash_directive_definition( - &mut self, - directive_definition: &Node, - ) -> Result<(), BoxError> { - "^DIRECTIVE_DEFINITION".hash(self); - directive_definition.name.as_str().hash(self); - "^ARGUMENT_LIST".hash(self); - for argument in &directive_definition.arguments { - self.hash_input_value_definition(argument)?; - } - "^ARGUMENT_LIST_END".hash(self); - - "^DIRECTIVE_DEFINITION-END".hash(self); - - Ok(()) - } - - fn hash_directive_list_schema(&mut self, directive_list: &schema::DirectiveList) { - "^DIRECTIVE_LIST".hash(self); - for directive in directive_list { - self.hash_directive(directive); - } - "^DIRECTIVE_LIST_END".hash(self); - } - - fn hash_directive_list_ast(&mut self, directive_list: &ast::DirectiveList) { - "^DIRECTIVE_LIST".hash(self); - for directive in directive_list { - self.hash_directive(directive); - } - "^DIRECTIVE_LIST_END".hash(self); - } - - fn hash_directive(&mut self, directive: &Node) { - "^DIRECTIVE".hash(self); - directive.name.as_str().hash(self); - "^ARGUMENT_LIST".hash(self); - for argument in &directive.arguments { - self.hash_argument(argument); - } - "^ARGUMENT_END".hash(self); - - "^DIRECTIVE-END".hash(self); - } - - fn hash_argument(&mut self, argument: &Node) { - "^ARGUMENT".hash(self); - argument.name.hash(self); - self.hash_value(&argument.value); - "^ARGUMENT-END".hash(self); - } - - fn hash_value(&mut self, value: &ast::Value) { - "^VALUE".hash(self); - - match value { - schema::Value::Null => "^null".hash(self), - schema::Value::Enum(e) => { - "^enum".hash(self); - e.hash(self); - } - schema::Value::Variable(v) => { - "^variable".hash(self); - v.hash(self); - } - schema::Value::String(s) => { - "^string".hash(self); - s.hash(self); - } - schema::Value::Float(f) => { - "^float".hash(self); - f.hash(self); - } - schema::Value::Int(i) => { - "^int".hash(self); - i.hash(self); - } - schema::Value::Boolean(b) => { - "^boolean".hash(self); - b.hash(self); - } - schema::Value::List(l) => { - "^list[".hash(self); - for v in l.iter() { - self.hash_value(v); - } - "^]".hash(self); - } - schema::Value::Object(o) => { - "^object{".hash(self); - for (k, v) in o.iter() { - "^key".hash(self); - - k.hash(self); - "^value:".hash(self); - self.hash_value(v); - } - "^}".hash(self); - } - } - "^VALUE-END".hash(self); - } - - fn hash_type_by_name(&mut self, name: &str) -> Result<(), BoxError> { - "^TYPE_BY_NAME".hash(self); - - name.hash(self); - - // we need this this to avoid an infinite loop when hashing types that refer to each other - if self.hashed_types.contains(name) { - return Ok(()); - } - - self.hashed_types.insert(name.to_string()); - - if let Some(ty) = self.schema.types.get(name) { - self.hash_extended_type(ty)?; - } - "^TYPE_BY_NAME-END".hash(self); - - Ok(()) - } - - fn hash_extended_type(&mut self, t: &'a ExtendedType) -> Result<(), BoxError> { - "^EXTENDED_TYPE".hash(self); - - match t { - ExtendedType::Scalar(s) => { - "^SCALAR".hash(self); - self.hash_directive_list_schema(&s.directives); - "^SCALAR_END".hash(self); - } - // this only hashes the type level info, not the fields, because those will be taken from the query - // we will still hash the fields using for the key - ExtendedType::Object(o) => { - "^OBJECT".hash(self); - - self.hash_directive_list_schema(&o.directives); - - self.hash_join_type(&o.name, &o.directives)?; - - self.record_context(&o.name, &o.directives)?; - - "^IMPLEMENTED_INTERFACES_LIST".hash(self); - for interface in &o.implements_interfaces { - self.hash_type_by_name(&interface.name)?; - } - "^IMPLEMENTED_INTERFACES_LIST_END".hash(self); - "^OBJECT_END".hash(self); - } - ExtendedType::Interface(i) => { - "^INTERFACE".hash(self); - - self.hash_directive_list_schema(&i.directives); - - self.hash_join_type(&i.name, &i.directives)?; - - self.record_context(&i.name, &i.directives)?; - - "^IMPLEMENTED_INTERFACES_LIST".hash(self); - for implementor in &i.implements_interfaces { - self.hash_type_by_name(&implementor.name)?; - } - "^IMPLEMENTED_INTERFACES_LIST_END".hash(self); - - if let Some(implementers) = self.implementers_map.get(&i.name) { - "^IMPLEMENTER_OBJECT_LIST".hash(self); - - for object in &implementers.objects { - self.hash_type_by_name(object)?; - } - "^IMPLEMENTER_OBJECT_LIST_END".hash(self); - - "^IMPLEMENTER_INTERFACE_LIST".hash(self); - for interface in &implementers.interfaces { - self.hash_type_by_name(interface)?; - } - "^IMPLEMENTER_INTERFACE_LIST_END".hash(self); - } - - "^INTERFACE_END".hash(self); - } - ExtendedType::Union(u) => { - "^UNION".hash(self); - - self.hash_directive_list_schema(&u.directives); - - self.record_context(&u.name, &u.directives)?; - - "^MEMBER_LIST".hash(self); - for member in &u.members { - self.hash_type_by_name(member.as_str())?; - } - "^MEMBER_LIST_END".hash(self); - "^UNION_END".hash(self); - } - ExtendedType::Enum(e) => { - "^ENUM".hash(self); - - self.hash_directive_list_schema(&e.directives); - - "^ENUM_VALUE_LIST".hash(self); - for (value, def) in &e.values { - "^VALUE".hash(self); - - value.hash(self); - self.hash_directive_list_ast(&def.directives); - "^VALUE_END".hash(self); - } - "^ENUM_VALUE_LIST_END".hash(self); - "^ENUM_END".hash(self); - } - ExtendedType::InputObject(o) => { - "^INPUT_OBJECT".hash(self); - self.hash_directive_list_schema(&o.directives); - - "^FIELD_LIST".hash(self); - for (name, ty) in &o.fields { - "^NAME".hash(self); - name.hash(self); - - "^ARGUMENT".hash(self); - self.hash_input_value_definition(&ty.node)?; - } - "^FIELD_LIST_END".hash(self); - "^INPUT_OBJECT_END".hash(self); - } - } - "^EXTENDED_TYPE-END".hash(self); - - Ok(()) - } - - fn hash_type(&mut self, t: &ast::Type) -> Result<(), BoxError> { - "^TYPE".hash(self); - - match t { - schema::Type::Named(name) => self.hash_type_by_name(name.as_str())?, - schema::Type::NonNullNamed(name) => { - "!".hash(self); - self.hash_type_by_name(name.as_str())?; - } - schema::Type::List(t) => { - "[]".hash(self); - self.hash_type(t)?; - } - schema::Type::NonNullList(t) => { - "[]!".hash(self); - self.hash_type(t)?; - } - } - "^TYPE-END".hash(self); - Ok(()) - } - - fn hash_field( - &mut self, - parent_type: &str, - field_def: &FieldDefinition, - node: &executable::Field, - ) -> Result<(), BoxError> { - "^FIELD".hash(self); - self.hash_field_definition(parent_type, field_def)?; - - "^ARGUMENT_LIST".hash(self); - for argument in &node.arguments { - self.hash_argument(argument); - } - "^ARGUMENT_LIST_END".hash(self); - - self.hash_directive_list_ast(&node.directives); - - node.alias.hash(self); - "^FIELD-END".hash(self); - - Ok(()) - } - - fn hash_field_definition( - &mut self, - parent_type: &str, - field_def: &FieldDefinition, - ) -> Result<(), BoxError> { - "^FIELD_DEFINITION".hash(self); - - let field_index = (parent_type.to_string(), field_def.name.as_str().to_string()); - if self.hashed_field_definitions.contains(&field_index) { - return Ok(()); - } - - self.hashed_field_definitions.insert(field_index); - - self.hash_type_by_name(parent_type)?; - - field_def.name.hash(self); - self.hash_type(&field_def.ty)?; - - // for every field, we also need to look at fields defined in `@requires` because - // they will affect the query plan - self.hash_join_field(parent_type, &field_def.directives)?; - - self.hash_directive_list_ast(&field_def.directives); - - "^ARGUMENT_DEF_LIST".hash(self); - for argument in &field_def.arguments { - self.hash_input_value_definition(argument)?; - } - "^ARGUMENT_DEF_LIST_END".hash(self); - - "^FIELD_DEFINITION_END".hash(self); - - Ok(()) - } - - fn hash_input_value_definition( - &mut self, - t: &Node, - ) -> Result<(), BoxError> { - "^INPUT_VALUE".hash(self); - - self.hash_type(&t.ty)?; - self.hash_directive_list_ast(&t.directives); - - if let Some(value) = t.default_value.as_ref() { - self.hash_value(value); - } else { - "^INPUT_VALUE-NO_DEFAULT".hash(self); - } - "^INPUT_VALUE-END".hash(self); - Ok(()) - } - - fn hash_join_type(&mut self, name: &Name, directives: &DirectiveList) -> Result<(), BoxError> { - "^JOIN_TYPE".hash(self); - - if let Some(dir_name) = self.join_type_directive_name.as_deref() { - if let Some(dir) = directives.get(dir_name) { - if let Some(key) = dir - .specified_argument_by_name("key") - .and_then(|arg| arg.as_str()) - { - let mut parser = Parser::new(); - if let Ok(field_set) = parser.parse_field_set( - Valid::assume_valid_ref(self.schema), - name.clone(), - key, - std::path::Path::new("schema.graphql"), - ) { - traverse::selection_set( - self, - name.as_str(), - &field_set.selection_set.selections[..], - )?; - } - } - } - } - "^JOIN_TYPE-END".hash(self); - - Ok(()) - } - - fn hash_join_field( - &mut self, - parent_type: &str, - directives: &ast::DirectiveList, - ) -> Result<(), BoxError> { - "^JOIN_FIELD".hash(self); - - if let Some(dir_name) = self.join_field_directive_name.as_deref() { - if let Some(dir) = directives.get(dir_name) { - if let Some(requires) = dir - .specified_argument_by_name("requires") - .and_then(|arg| arg.as_str()) - { - if let Ok(parent_type) = Name::new(parent_type) { - let mut parser = Parser::new(); - - if let Ok(field_set) = parser.parse_field_set( - Valid::assume_valid_ref(self.schema), - parent_type.clone(), - requires, - std::path::Path::new("schema.graphql"), - ) { - traverse::selection_set( - self, - parent_type.as_str(), - &field_set.selection_set.selections[..], - )?; - } - } - } - - if let Some(context_arguments) = dir - .specified_argument_by_name("contextArguments") - .and_then(|value| value.as_list()) - { - for argument in context_arguments { - self.hash_context_argument(argument)?; - } - } - } - } - "^JOIN_FIELD-END".hash(self); - - Ok(()) - } - - fn record_context( - &mut self, - parent_type: &str, - directives: &DirectiveList, - ) -> Result<(), BoxError> { - if let Some(dir_name) = self.context_directive_name.as_deref() { - if let Some(dir) = directives.get(dir_name) { - if let Some(name) = dir - .specified_argument_by_name("name") - .and_then(|arg| arg.as_str()) - { - self.contexts - .entry(name.to_string()) - .or_default() - .push(parent_type.to_string()); - } - } - } - Ok(()) - } - - /// Hashes the context argument of a field - /// - /// contextArgument contains a selection that must be applied to a parent type in the - /// query that matches the context name. We store in advance which type names map to - /// which contexts, to reuse them here when we encounter the selection. - fn hash_context_argument(&mut self, argument: &ast::Value) -> Result<(), BoxError> { - if let Some(obj) = argument.as_object() { - let context_name = Name::new("context")?; - let selection_name = Name::new("selection")?; - // the contextArgument input type is defined as follows: - // input join__ContextArgument { - // name: String! - // type: String! - // context: String! - // selection: join__FieldValue! - // } - // and that is checked by schema validation, so the `context` and `selection` fields - // are guaranteed to be present and to be strings. - if let (Some(context), Some(selection)) = ( - obj.iter() - .find(|(k, _)| k == &context_name) - .and_then(|(_, v)| v.as_str()), - obj.iter() - .find(|(k, _)| k == &selection_name) - .and_then(|(_, v)| v.as_str()), - ) { - if let Some(types) = self.contexts.get(context).cloned() { - for ty in types { - if let Ok(parent_type) = Name::new(ty.as_str()) { - let mut parser = Parser::new(); - - // we assume that the selection was already checked by schema validation - if let Ok(field_set) = parser.parse_field_set( - Valid::assume_valid_ref(self.schema), - parent_type.clone(), - selection, - std::path::Path::new("schema.graphql"), - ) { - traverse::selection_set( - self, - parent_type.as_str(), - &field_set.selection_set.selections[..], - )?; - } - } - } - } - } - Ok(()) - } else { - Err("context argument value is not an object".into()) - } - } - - fn hash_interface_implementers( - &mut self, - intf: &InterfaceType, - node: &executable::Field, - ) -> Result<(), BoxError> { - "^INTERFACE_IMPL".hash(self); - - if let Some(implementers) = self.implementers_map.get(&intf.name) { - "^IMPLEMENTER_LIST".hash(self); - for object in &implementers.objects { - self.hash_type_by_name(object)?; - traverse::selection_set(self, object, &node.selection_set.selections)?; - } - "^IMPLEMENTER_LIST_END".hash(self); - } - - "^INTERFACE_IMPL-END".hash(self); - Ok(()) - } -} - -impl Hasher for QueryHashVisitor<'_> { - fn finish(&self) -> u64 { - unreachable!() - } - - fn write(&mut self, bytes: &[u8]) { - // byte separator between each part that is hashed - self.hasher.update(&[0xFF][..]); - self.hasher.update(bytes); - } -} - -impl Visitor for QueryHashVisitor<'_> { - fn operation(&mut self, root_type: &str, node: &executable::Operation) -> Result<(), BoxError> { - "^VISIT_OPERATION".hash(self); - - root_type.hash(self); - self.hash_type_by_name(root_type)?; - node.operation_type.hash(self); - node.name.hash(self); - - "^VARIABLE_LIST".hash(self); - for variable in &node.variables { - variable.name.hash(self); - self.hash_type(&variable.ty)?; - - if let Some(value) = variable.default_value.as_ref() { - self.hash_value(value); - } else { - "^VISIT_OPERATION-NO_DEFAULT".hash(self); - } - - self.hash_directive_list_ast(&variable.directives); - } - "^VARIABLE_LIST_END".hash(self); - - self.hash_directive_list_ast(&node.directives); - - traverse::operation(self, root_type, node)?; - "^VISIT_OPERATION-END".hash(self); - Ok(()) - } - - fn field( - &mut self, - parent_type: &str, - field_def: &ast::FieldDefinition, - node: &executable::Field, - ) -> Result<(), BoxError> { - "^VISIT_FIELD".hash(self); - - if !self.seen_introspection && (field_def.name == "__schema" || field_def.name == "__type") - { - self.seen_introspection = true; - self.schema_str.hash(self); - } - - self.hash_field(parent_type, field_def, node)?; - - if let Some(ExtendedType::Interface(intf)) = - self.schema.types.get(field_def.ty.inner_named_type()) - { - self.hash_interface_implementers(intf, node)?; - } - - traverse::field(self, field_def, node)?; - "^VISIT_FIELD_END".hash(self); - Ok(()) - } - - fn fragment(&mut self, node: &executable::Fragment) -> Result<(), BoxError> { - "^VISIT_FRAGMENT".hash(self); - - node.name.hash(self); - self.hash_type_by_name(node.type_condition())?; - - self.hash_directive_list_ast(&node.directives); - - traverse::fragment(self, node)?; - "^VISIT_FRAGMENT-END".hash(self); - - Ok(()) - } - - fn fragment_spread(&mut self, node: &executable::FragmentSpread) -> Result<(), BoxError> { - "^VISIT_FRAGMENT_SPREAD".hash(self); - - node.fragment_name.hash(self); - let type_condition = &self - .fragments - .get(&node.fragment_name) - .ok_or("MissingFragment")? - .type_condition(); - self.hash_type_by_name(type_condition)?; - - self.hash_directive_list_ast(&node.directives); - - traverse::fragment_spread(self, node)?; - "^VISIT_FRAGMENT_SPREAD-END".hash(self); - - Ok(()) - } - - fn inline_fragment( - &mut self, - parent_type: &str, - node: &executable::InlineFragment, - ) -> Result<(), BoxError> { - "^VISIT_INLINE_FRAGMENT".hash(self); - - if let Some(type_condition) = &node.type_condition { - self.hash_type_by_name(type_condition)?; - } - self.hash_directive_list_ast(&node.directives); - - traverse::inline_fragment(self, parent_type, node)?; - "^VISIT_INLINE_FRAGMENT-END".hash(self); - Ok(()) - } - - fn schema(&self) -> &apollo_compiler::Schema { - self.schema - } -} - -#[cfg(test)] -mod tests { - use apollo_compiler::ast::Document; - use apollo_compiler::schema::Schema; - use apollo_compiler::validation::Valid; - - use super::QueryHashVisitor; - use crate::spec::query::traverse; - - #[derive(Debug)] - struct HashComparator { - from_visitor: String, - from_hash_query: String, - } - - impl From<(String, String)> for HashComparator { - fn from(value: (String, String)) -> Self { - Self { - from_visitor: value.0, - from_hash_query: value.1, - } - } - } - - // The non equality check is not the same - // as one would expect from PartialEq. - // This is why HashComparator doesn't implement it. - impl HashComparator { - fn equals(&self, other: &Self) -> bool { - self.from_visitor == other.from_visitor && self.from_hash_query == other.from_hash_query - } - fn doesnt_match(&self, other: &Self) -> bool { - // This is intentional, we check to prevent BOTH hashes from being equal - self.from_visitor != other.from_visitor && self.from_hash_query != other.from_hash_query - } - } - - #[track_caller] - fn hash(schema_str: &str, query: &str) -> HashComparator { - let schema = Schema::parse(schema_str, "schema.graphql") - .unwrap() - .validate() - .unwrap(); - let doc = Document::parse(query, "query.graphql").unwrap(); - - let exec = doc - .to_executable(&schema) - .unwrap() - .validate(&schema) - .unwrap(); - let implementers_map = schema.implementers_map(); - let mut visitor = - QueryHashVisitor::new(&schema, schema_str, &implementers_map, &exec).unwrap(); - traverse::document(&mut visitor, &exec, None).unwrap(); - - ( - hex::encode(visitor.finish()), - hex::encode( - QueryHashVisitor::hash_query(&schema, schema_str, &implementers_map, &exec, None) - .unwrap(), - ), - ) - .into() - } - - #[track_caller] - fn hash_subgraph_query(schema_str: &str, query: &str) -> String { - let schema = Valid::assume_valid(Schema::parse(schema_str, "schema.graphql").unwrap()); - let doc = Document::parse(query, "query.graphql").unwrap(); - let exec = doc - .to_executable(&schema) - .unwrap() - .validate(&schema) - .unwrap(); - let implementers_map = schema.implementers_map(); - let mut visitor = - QueryHashVisitor::new(&schema, schema_str, &implementers_map, &exec).unwrap(); - traverse::document(&mut visitor, &exec, None).unwrap(); - - hex::encode(visitor.finish()) - } - - #[test] - fn me() { - let schema1: &str = r#" - type Query { - me: User - customer: User - } - - type User { - id: ID - name: String - } - "#; - - let schema2: &str = r#" - type Query { - me: User - } - - - type User { - id: ID! - name: String - } - "#; - let query = "query { me { name } }"; - assert!(hash(schema1, query).equals(&hash(schema2, query))); - - // id is nullable in 1, non nullable in 2 - let query = "query { me { id name } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - // simple normalization - let query = "query { moi: me { name } }"; - assert!(hash(schema1, query).equals(&hash(schema2, query))); - - assert!(hash(schema1, "query { me { id name } }") - .doesnt_match(&hash(schema1, "query { me { name id } }"))); - } - - #[test] - fn directive() { - let schema1: &str = r#" - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM | UNION | INPUT_OBJECT - - type Query { - me: User - customer: User - s: S - u: U - e: E - inp(i: I): ID - } - - type User { - id: ID! - name: String - } - - scalar S - - type A { - a: ID - } - - type B { - b: ID - } - - union U = A | B - - enum E { - A - B - } - - input I { - a: Int = 0 - b: Int - } - "#; - - let schema2: &str = r#" - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM | UNION | INPUT_OBJECT - - type Query { - me: User - customer: User @test - s: S - u: U - e: E - inp(i: I): ID - } - - type User { - id: ID! @test - name: String - } - - scalar S @test - - type A { - a: ID - } - - type B { - b: ID - } - - union U @test = A | B - - enum E @test { - A - B - } - - - input I @test { - a: Int = 0 - b: Int - } - "#; - let query = "query { me { name } }"; - assert!(hash(schema1, query).equals(&hash(schema2, query))); - - let query = "query { me { id name } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { customer { id } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { s }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { u { ...on A { a } ...on B { b } } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { e }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { inp(i: { b: 0 }) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn interface() { - let schema1: &str = r#" - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM - - type Query { - me: User - customer: I - } - - interface I { - id: ID - } - - type User implements I { - id: ID! - name: String - } - "#; - - let schema2: &str = r#" - schema { - query: Query - } - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM - - type Query { - me: User - customer: I - } - - interface I @test { - id: ID - } - - type User implements I { - id: ID! - name: String - } - "#; - - let query = "query { me { id name } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { customer { id } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { customer { ... on User { name } } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn arguments_int() { - let schema1: &str = r#" - type Query { - a(i: Int): Int - b(i: Int = 1): Int - c(i: Int = 1, j: Int = null): Int - } - "#; - - let schema2: &str = r#" - type Query { - a(i: Int!): Int - b(i: Int = 2): Int - c(i: Int = 2, j: Int = null): Int - } - "#; - - let query = "query { a(i: 0) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b(i: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(j: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(i:0, j: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn arguments_float() { - let schema1: &str = r#" - type Query { - a(i: Float): Int - b(i: Float = 1.0): Int - c(i: Float = 1.0, j: Int): Int - } - "#; - - let schema2: &str = r#" - type Query { - a(i: Float!): Int - b(i: Float = 2.0): Int - c(i: Float = 2.0, j: Int): Int - } - "#; - - let query = "query { a(i: 0) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b(i: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(j: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(i:0, j: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn arguments_list() { - let schema1: &str = r#" - type Query { - a(i: [Float]): Int - b(i: [Float] = [1.0]): Int - c(i: [Float] = [1.0], j: Int): Int - } - "#; - - let schema2: &str = r#" - type Query { - a(i: [Float!]): Int - b(i: [Float] = [2.0]): Int - c(i: [Float] = [2.0], j: Int): Int - } - "#; - - let query = "query { a(i: [0]) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b(i: [0])}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(j: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(i: [0], j: 0)}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn arguments_object() { - let schema1: &str = r#" - input T { - d: Int - e: String - } - input U { - c: Int - } - input V { - d: Int = 0 - } - - type Query { - a(i: T): Int - b(i: T = { d: 1, e: "a" }): Int - c(c: U): Int - d(d: V): Int - } - "#; - - let schema2: &str = r#" - input T { - d: Int - e: String - } - input U { - c: Int! - } - input V { - d: Int = 1 - } - - type Query { - a(i: T!): Int - b(i: T = { d: 2, e: "b" }): Int - c(c: U): Int - d(d: V): Int - } - "#; - - let query = "query { a(i: { d: 1, e: \"a\" }) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { b(i: { d: 3, e: \"c\" })}"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { c(c: { c: 0 }) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { d(d: { }) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { d(d: { d: 2 }) }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn entities() { - let schema1: &str = r#" - scalar _Any - - union _Entity = User - - type Query { - _entities(representations: [_Any!]!): [_Entity]! - me: User - customer: User - } - - type User { - id: ID - name: String - } - "#; - - let schema2: &str = r#" - scalar _Any - - union _Entity = User - - type Query { - _entities(representations: [_Any!]!): [_Entity]! - me: User - } - - - type User { - id: ID! - name: String - } - "#; - - let query1 = r#"query Query1($representations:[_Any!]!){ - _entities(representations:$representations){ - ...on User { - id - name - } - } - }"#; - - let hash1 = hash_subgraph_query(schema1, query1); - let hash2 = hash_subgraph_query(schema2, query1); - assert_ne!(hash1, hash2); - - let query2 = r#"query Query1($representations:[_Any!]!){ - _entities(representations:$representations){ - ...on User { - name - } - } - }"#; - - let hash1 = hash_subgraph_query(schema1, query2); - let hash2 = hash_subgraph_query(schema2, query2); - assert_eq!(hash1, hash2); - } - - #[test] - fn join_type_key() { - let schema1: &str = r#" - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { - query: Query - } - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM - directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE - directive @join__graph(name: String!, url: String!) on ENUM_VALUE - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - scalar join__FieldSet - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - enum join__Graph { - ACCOUNTS @join__graph(name: "accounts", url: "https://accounts.demo.starstuff.dev") - INVENTORY @join__graph(name: "inventory", url: "https://inventory.demo.starstuff.dev") - PRODUCTS @join__graph(name: "products", url: "https://products.demo.starstuff.dev") - REVIEWS @join__graph(name: "reviews", url: "https://reviews.demo.starstuff.dev") - } - - type Query { - me: User - customer: User - itf: I - } - - type User @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String - } - - interface I @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name :String - } - - union U = User - "#; - - let schema2: &str = r#" - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { - query: Query - } - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM - directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE - directive @join__graph(name: String!, url: String!) on ENUM_VALUE - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - scalar join__FieldSet - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - enum join__Graph { - ACCOUNTS @join__graph(name: "accounts", url: "https://accounts.demo.starstuff.dev") - INVENTORY @join__graph(name: "inventory", url: "https://inventory.demo.starstuff.dev") - PRODUCTS @join__graph(name: "products", url: "https://products.demo.starstuff.dev") - REVIEWS @join__graph(name: "reviews", url: "https://reviews.demo.starstuff.dev") - } - - type Query { - me: User - customer: User @test - itf: I - } - - type User @join__type(graph: ACCOUNTS, key: "id") { - id: ID! @test - name: String - } - - interface I @join__type(graph: ACCOUNTS, key: "id") { - id: ID! @test - name :String - } - "#; - let query = "query { me { name } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { itf { name } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn join_field_requires() { - let schema1: &str = r#" - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { - query: Query - } - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM - directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE - directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - directive @join__graph(name: String!, url: String!) on ENUM_VALUE - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - scalar join__FieldSet - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - enum join__Graph { - ACCOUNTS @join__graph(name: "accounts", url: "https://accounts.demo.starstuff.dev") - INVENTORY @join__graph(name: "inventory", url: "https://inventory.demo.starstuff.dev") - PRODUCTS @join__graph(name: "products", url: "https://products.demo.starstuff.dev") - REVIEWS @join__graph(name: "reviews", url: "https://reviews.demo.starstuff.dev") - } - - type Query { - me: User - customer: User - itf: I - } - - type User @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String - username: String @join__field(graph:ACCOUNTS, requires: "name") - a: String @join__field(graph:ACCOUNTS, requires: "itf { ... on A { name } }") - itf: I - } - - interface I @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String - } - - type A implements I @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String - } - "#; - - let schema2: &str = r#" - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { - query: Query - } - directive @test on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM - directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE - directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - directive @join__graph(name: String!, url: String!) on ENUM_VALUE - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - scalar join__FieldSet - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - enum join__Graph { - ACCOUNTS @join__graph(name: "accounts", url: "https://accounts.demo.starstuff.dev") - INVENTORY @join__graph(name: "inventory", url: "https://inventory.demo.starstuff.dev") - PRODUCTS @join__graph(name: "products", url: "https://products.demo.starstuff.dev") - REVIEWS @join__graph(name: "reviews", url: "https://reviews.demo.starstuff.dev") - } - - type Query { - me: User - customer: User @test - itf: I - } - - type User @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String @test - username: String @join__field(graph:ACCOUNTS, requires: "name") - a: String @join__field(graph:ACCOUNTS, requires: "itf { ... on A { name } }") - itf: I - } - - interface I @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String @test - } - - type A implements I @join__type(graph: ACCOUNTS, key: "id") { - id: ID! - name: String @test - } - "#; - let query = "query { me { username } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - - let query = "query { me { a } }"; - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn introspection() { - let schema1: &str = r#" - schema { - query: Query - } - - type Query { - me: User - customer: User - } - - type User { - id: ID - name: String - } - "#; - - let schema2: &str = r#" - schema { - query: Query - } - - type Query { - me: NotUser - } - - - type NotUser { - id: ID! - name: String - } - "#; - - let query = "{ __schema { types { name } } }"; - - assert!(hash(schema1, query).doesnt_match(&hash(schema2, query))); - } - - #[test] - fn fields_with_different_arguments_have_different_hashes() { - let schema: &str = r#" - type Query { - test(arg: Int): String - } - "#; - - let query_one = "query { a: test(arg: 1) b: test(arg: 2) }"; - let query_two = "query { a: test(arg: 1) b: test(arg: 3) }"; - - // This assertion tests an internal hash function that isn't directly - // used for the query hash, and we'll need to make it pass to rely - // solely on the internal function again. - // - // assert!(hash(schema, query_one).doesnt_match(&hash(schema, - // query_two))); - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - } - - #[test] - fn fields_with_different_arguments_on_nest_field_different_hashes() { - let schema: &str = r#" - type Test { - test(arg: Int): String - recursiveLink: Test - } - - type Query { - directLink: Test - } - "#; - - let query_one = "{ directLink { test recursiveLink { test(arg: 1) } } }"; - let query_two = "{ directLink { test recursiveLink { test(arg: 2) } } }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn fields_with_different_aliases_have_different_hashes() { - let schema: &str = r#" - type Query { - test(arg: Int): String - } - "#; - - let query_one = "{ a: test }"; - let query_two = "{ b: test }"; - - // This assertion tests an internal hash function that isn't directly - // used for the query hash, and we'll need to make it pass to rely - // solely on the internal function again. - // - // assert!(hash(schema, query_one).doesnt_match(&hash(schema, query_two))); - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - } - - #[test] - fn operations_with_different_names_have_different_hash() { - let schema: &str = r#" - type Query { - test: String - } - "#; - - let query_one = "query Foo { test }"; - let query_two = "query Bar { test }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn adding_directive_on_operation_changes_hash() { - let schema: &str = r#" - directive @test on QUERY - type Query { - test: String - } - "#; - - let query_one = "query { test }"; - let query_two = "query @test { test }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn order_of_variables_changes_hash() { - let schema: &str = r#" - type Query { - test1(arg: Int): String - test2(arg: Int): String - } - "#; - - let query_one = "query ($foo: Int, $bar: Int) { test1(arg: $foo) test2(arg: $bar) }"; - let query_two = "query ($foo: Int, $bar: Int) { test1(arg: $bar) test2(arg: $foo) }"; - - assert!(hash(schema, query_one).doesnt_match(&hash(schema, query_two))); - } - - #[test] - fn query_variables_with_different_types_have_different_hash() { - let schema: &str = r#" - type Query { - test(arg: Int): String - } - "#; - - let query_one = "query ($var: Int) { test(arg: $var) }"; - let query_two = "query ($var: Int!) { test(arg: $var) }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn query_variables_with_different_default_values_have_different_hash() { - let schema: &str = r#" - type Query { - test(arg: Int): String - } - "#; - - let query_one = "query ($var: Int = 1) { test(arg: $var) }"; - let query_two = "query ($var: Int = 2) { test(arg: $var) }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn adding_directive_to_query_variable_change_hash() { - let schema: &str = r#" - directive @test on VARIABLE_DEFINITION - - type Query { - test(arg: Int): String - } - "#; - - let query_one = "query ($var: Int) { test(arg: $var) }"; - let query_two = "query ($var: Int @test) { test(arg: $var) }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn order_of_directives_change_hash() { - let schema: &str = r#" - directive @foo on FIELD - directive @bar on FIELD - - type Query { - test(arg: Int): String - } - "#; - - let query_one = "{ test @foo @bar }"; - let query_two = "{ test @bar @foo }"; - - assert!(hash(schema, query_one).from_hash_query != hash(schema, query_two).from_hash_query); - assert!(hash(schema, query_one).from_visitor != hash(schema, query_two).from_visitor); - } - - #[test] - fn directive_argument_type_change_hash() { - let schema1: &str = r#" - directive @foo(a: Int) on FIELD - directive @bar on FIELD - - type Query { - test(arg: Int): String - } - "#; - - let schema2: &str = r#" - directive @foo(a: Int!) on FIELD - directive @bar on FIELD - - type Query { - test(arg: Int): String - } - "#; - - let query = "{ test @foo(a: 1) }"; - - assert!(hash(schema1, query).from_hash_query != hash(schema2, query).from_hash_query); - assert!(hash(schema1, query).from_visitor != hash(schema2, query).from_visitor); - } - - #[test] - fn adding_directive_on_schema_changes_hash() { - let schema1: &str = r#" - schema { - query: Query - } - - type Query { - foo: String - } - "#; - - let schema2: &str = r#" - directive @test on SCHEMA - schema @test { - query: Query - } - - type Query { - foo: String - } - "#; - - let query = "{ foo }"; - - assert!(hash(schema1, query).from_hash_query != hash(schema2, query).from_hash_query); - assert!(hash(schema1, query).from_visitor != hash(schema2, query).from_visitor); - } - - #[test] - fn changing_type_of_field_changes_hash() { - let schema1: &str = r#" - type Query { - test: Int - } - "#; - - let schema2: &str = r#" - type Query { - test: Float - } - "#; - - let query = "{ test }"; - - assert!(hash(schema1, query).from_hash_query != hash(schema2, query).from_hash_query); - assert!(hash(schema1, query).from_visitor != hash(schema2, query).from_visitor); - } - - #[test] - fn changing_type_to_interface_changes_hash() { - let schema1: &str = r#" - type Query { - foo: Foo - } - - interface Foo { - value: String - } - "#; - - let schema2: &str = r#" - type Query { - foo: Foo - } - - type Foo { - value: String - } - "#; - - let query = "{ foo { value } }"; - - assert!(hash(schema1, query).from_hash_query != hash(schema2, query).from_hash_query); - assert!(hash(schema1, query).from_visitor != hash(schema2, query).from_visitor); - } - - #[test] - fn changing_operation_kind_changes_hash() { - let schema: &str = r#" - schema { - query: Test - mutation: Test - } - - type Test { - test: String - } - "#; - - let query_one = "query { test }"; - let query_two = "mutation { test }"; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn adding_directive_on_field_should_change_hash() { - let schema: &str = r#" - directive @test on FIELD - - type Query { - test: String - } - "#; - - let query_one = "{ test }"; - let query_two = "{ test @test }"; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn adding_directive_on_fragment_spread_change_hash() { - let schema: &str = r#" - type Query { - test: String - } - "#; - - let query_one = r#" - { ...Test } - - fragment Test on Query { - test - } - "#; - let query_two = r#" - { ...Test @skip(if: false) } - - fragment Test on Query { - test - } - "#; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn adding_directive_on_fragment_change_hash() { - let schema: &str = r#" - directive @test on FRAGMENT_DEFINITION - - type Query { - test: String - } - "#; - - let query_one = r#" - { ...Test } - - fragment Test on Query { - test - } - "#; - let query_two = r#" - { ...Test } - - fragment Test on Query @test { - test - } - "#; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn adding_directive_on_inline_fragment_change_hash() { - let schema: &str = r#" - type Query { - test: String - } - "#; - - let query_one = "{ ... { test } }"; - let query_two = "{ ... @skip(if: false) { test } }"; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn moving_field_changes_hash() { - let schema: &str = r#" - type Query { - me: User - } - - type User { - id: ID - name: String - friend: User - } - "#; - - let query_one = r#" - { - me { - friend { - id - name - } - } - } - "#; - let query_two = r#" - { - me { - friend { - id - } - name - } - } - "#; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn changing_type_of_fragment_changes_hash() { - let schema: &str = r#" - type Query { - fooOrBar: FooOrBar - } - - type Foo { - id: ID - value: String - } - - type Bar { - id: ID - value: String - } - - union FooOrBar = Foo | Bar - "#; - - let query_one = r#" - { - fooOrBar { - ... on Foo { id } - ... on Bar { id } - ... Test - } - } - - fragment Test on Foo { - value - } - "#; - let query_two = r#" - { - fooOrBar { - ... on Foo { id } - ... on Bar { id } - ... Test - } - } - - fragment Test on Bar { - value - } - "#; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn changing_interface_implementors_changes_hash() { - let schema1: &str = r#" - type Query { - data: I - } - - interface I { - id: ID - value: String - } - - type Foo implements I { - id: ID - value: String - foo: String - } - - type Bar { - id: ID - value: String - bar: String - } - "#; - - let schema2: &str = r#" - type Query { - data: I - } - - interface I { - id: ID - value: String - } - - type Foo implements I { - id: ID - value: String - foo2: String - } - - type Bar { - id: ID - value: String - bar: String - } - "#; - - let schema3: &str = r#" - type Query { - data: I - } - - interface I { - id: ID - value: String - } - - type Foo implements I { - id: ID - value: String - foo: String - } - - type Bar implements I { - id: ID - value: String - bar: String - } - "#; - - let query = r#" - { - data { - id - value - } - } - "#; - - // changing an unrelated field in implementors does not change the hash - assert_eq!( - hash(schema1, query).from_hash_query, - hash(schema2, query).from_hash_query - ); - assert_eq!( - hash(schema1, query).from_visitor, - hash(schema2, query).from_visitor - ); - - // adding a new implementor changes the hash - assert_ne!( - hash(schema1, query).from_hash_query, - hash(schema3, query).from_hash_query - ); - assert_ne!( - hash(schema1, query).from_visitor, - hash(schema3, query).from_visitor - ); - } - - #[test] - fn changing_interface_directives_changes_hash() { - let schema1: &str = r#" - directive @a(name: String) on INTERFACE - - type Query { - data: I - } - - interface I @a { - id: ID - value: String - } - - type Foo implements I { - id: ID - value: String - foo: String - } - "#; - - let schema2: &str = r#" - directive @a(name: String) on INTERFACE - - type Query { - data: I - } - - interface I @a(name: "abc") { - id: ID - value: String - } - - type Foo implements I { - id: ID - value: String - foo2: String - } - - "#; - - let query = r#" - { - data { - id - value - } - } - "#; - - // changing a directive applied on the interface definition changes the hash - assert_ne!( - hash(schema1, query).from_hash_query, - hash(schema2, query).from_hash_query - ); - assert_ne!( - hash(schema1, query).from_visitor, - hash(schema2, query).from_visitor - ); - } - - #[test] - fn it_is_weird_so_i_dont_know_how_to_name_it_change_hash() { - let schema: &str = r#" - type Query { - id: ID - someField: SomeType - test: String - } - - type SomeType { - id: ID - test: String - } - "#; - - let query_one = r#" - { - test - someField { id test } - id - } - "#; - let query_two = r#" - { - ...test - someField { id } - } - - fragment test on Query { - id - } - "#; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn it_change_directive_location() { - let schema: &str = r#" - directive @foo on QUERY | VARIABLE_DEFINITION - - type Query { - field(arg: String): String - } - "#; - - let query_one = r#" - query Test ($arg: String @foo) { - field(arg: $arg) - } - "#; - let query_two = r#" - query Test ($arg: String) @foo { - field(arg: $arg) - } - "#; - - assert_ne!( - hash(schema, query_one).from_hash_query, - hash(schema, query_two).from_hash_query - ); - assert_ne!( - hash(schema, query_one).from_visitor, - hash(schema, query_two).from_visitor - ); - } - - #[test] - fn it_changes_on_implementors_list_changes() { - let schema_one: &str = r#" - interface SomeInterface { - value: String - } - - type Foo implements SomeInterface { - value: String - } - - type Bar implements SomeInterface { - value: String - } - - union FooOrBar = Foo | Bar - - type Query { - fooOrBar: FooOrBar - } - "#; - let schema_two: &str = r#" - interface SomeInterface { - value: String - } - - type Foo { - value: String # <= This field shouldn't be a part of query plan anymore - } - - type Bar implements SomeInterface { - value: String - } - - union FooOrBar = Foo | Bar - - type Query { - fooOrBar: FooOrBar - } - "#; - - let query = r#" - { - fooOrBar { - ... on SomeInterface { - value - } - } - } - "#; - - assert_ne!( - hash(schema_one, query).from_hash_query, - hash(schema_two, query).from_hash_query - ); - assert_ne!( - hash(schema_one, query).from_visitor, - hash(schema_two, query).from_visitor - ); - } - - #[test] - fn it_changes_on_context_changes() { - let schema_one: &str = r#" - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) - @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) { - query: Query -} - -directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION - -directive @context__fromContext(field: String) on ARGUMENT_DEFINITION - -directive @join__directive( - graphs: [join__Graph!] - name: String! - args: join__DirectiveArguments -) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - -directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - -directive @join__field( - graph: join__Graph - requires: join__FieldSet - provides: join__FieldSet - type: String - external: Boolean - override: String - usedOverridden: Boolean - overrideLabel: String - contextArguments: [join__ContextArgument!] -) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - -directive @join__graph(name: String!, url: String!) on ENUM_VALUE - -directive @join__implements( - graph: join__Graph! - interface: String! -) repeatable on OBJECT | INTERFACE - -directive @join__type( - graph: join__Graph! - key: join__FieldSet - extension: Boolean! = false - resolvable: Boolean! = true - isInterfaceObject: Boolean! = false -) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR - -directive @join__unionMember( - graph: join__Graph! - member: String! -) repeatable on UNION - -directive @link( - url: String - as: String - for: link__Purpose - import: [link__Import] -) repeatable on SCHEMA - -scalar context__context - -input join__ContextArgument { - name: String! - type: String! - context: String! - selection: join__FieldValue! -} - -scalar join__DirectiveArguments - -scalar join__FieldSet - -scalar join__FieldValue - -enum join__Graph { - SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://Subgraph1") - SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://Subgraph2") -} - -scalar link__Import - -enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION -} - -type Query @join__type(graph: SUBGRAPH1) { - t: T! @join__field(graph: SUBGRAPH1) -} - - -type T - @join__type(graph: SUBGRAPH1, key: "id") - @context(name: "Subgraph1__context") { - id: ID! - u: U! - uList: [U]! - prop: String! -} - -type U - @join__type(graph: SUBGRAPH1, key: "id") - @join__type(graph: SUBGRAPH2, key: "id") { - id: ID! - b: String! @join__field(graph: SUBGRAPH2) - field: Int! - @join__field( - graph: SUBGRAPH1 - contextArguments: [ - { - context: "Subgraph1__context" - name: "a" - type: "String" - selection: "{ prop }" - } - ] - ) -} - "#; - - // changing T.prop from String! to String - let schema_two: &str = r#" - schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) - @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) { - query: Query -} - -directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION - -directive @context__fromContext(field: String) on ARGUMENT_DEFINITION - -directive @join__directive( - graphs: [join__Graph!] - name: String! - args: join__DirectiveArguments -) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - -directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - -directive @join__field( - graph: join__Graph - requires: join__FieldSet - provides: join__FieldSet - type: String - external: Boolean - override: String - usedOverridden: Boolean - overrideLabel: String - contextArguments: [join__ContextArgument!] -) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION - -directive @join__graph(name: String!, url: String!) on ENUM_VALUE - -directive @join__implements( - graph: join__Graph! - interface: String! -) repeatable on OBJECT | INTERFACE - -directive @join__type( - graph: join__Graph! - key: join__FieldSet - extension: Boolean! = false - resolvable: Boolean! = true - isInterfaceObject: Boolean! = false -) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR - -directive @join__unionMember( - graph: join__Graph! - member: String! -) repeatable on UNION - -directive @link( - url: String - as: String - for: link__Purpose - import: [link__Import] -) repeatable on SCHEMA - -scalar context__context - -input join__ContextArgument { - name: String! - type: String! - context: String! - selection: join__FieldValue! -} - -scalar join__DirectiveArguments - -scalar join__FieldSet - -scalar join__FieldValue - -enum join__Graph { - SUBGRAPH1 @join__graph(name: "Subgraph1", url: "https://Subgraph1") - SUBGRAPH2 @join__graph(name: "Subgraph2", url: "https://Subgraph2") -} - -scalar link__Import - -enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION -} - -type Query @join__type(graph: SUBGRAPH1) { - t: T! @join__field(graph: SUBGRAPH1) -} - - -type T - @join__type(graph: SUBGRAPH1, key: "id") - @context(name: "Subgraph1__context") { - id: ID! - u: U! - uList: [U]! - prop: String -} - -type U - @join__type(graph: SUBGRAPH1, key: "id") - @join__type(graph: SUBGRAPH2, key: "id") { - id: ID! - b: String! @join__field(graph: SUBGRAPH2) - field: Int! - @join__field( - graph: SUBGRAPH1 - contextArguments: [ - { - context: "Subgraph1__context" - name: "a" - type: "String" - selection: "{ prop }" - } - ] - ) -} - "#; - - let query = r#" - query Query { - t { - __typename - id - u { - __typename - field - } - } - } - "#; - - assert_ne!( - hash(schema_one, query).from_hash_query, - hash(schema_two, query).from_hash_query - ); - assert_ne!( - hash(schema_one, query).from_visitor, - hash(schema_two, query).from_visitor - ); - } -} diff --git a/apollo-router/src/spec/query/tests.rs b/apollo-router/src/spec/query/tests.rs index 1d97962405..5b6288adfb 100644 --- a/apollo-router/src/spec/query/tests.rs +++ b/apollo-router/src/spec/query/tests.rs @@ -5766,7 +5766,7 @@ fn filtered_defer_fragment() { .unwrap(); let doc = ast.to_executable(schema.supergraph_schema()).unwrap(); let (fragments, operation, defer_stats, schema_aware_hash) = - Query::extract_query_information(&schema, &doc, None).unwrap(); + Query::extract_query_information(&schema, filtered_query, &doc, None).unwrap(); let subselections = crate::spec::query::subselections::collect_subselections( &config, @@ -5792,7 +5792,7 @@ fn filtered_defer_fragment() { .unwrap(); let doc = ast.to_executable(schema.supergraph_schema()).unwrap(); let (fragments, operation, defer_stats, schema_aware_hash) = - Query::extract_query_information(&schema, &doc, None).unwrap(); + Query::extract_query_information(&schema, filtered_query, &doc, None).unwrap(); let subselections = crate::spec::query::subselections::collect_subselections( &config, diff --git a/apollo-router/src/spec/schema.rs b/apollo-router/src/spec/schema.rs index f4e79b5ff2..50e7a48b64 100644 --- a/apollo-router/src/spec/schema.rs +++ b/apollo-router/src/spec/schema.rs @@ -1,6 +1,7 @@ //! GraphQL schema. use std::collections::HashMap; +use std::fmt::Display; use std::str::FromStr; use std::sync::Arc; use std::time::Instant; @@ -17,6 +18,8 @@ use apollo_federation::Supergraph; use http::Uri; use semver::Version; use semver::VersionReq; +use serde::Deserialize; +use serde::Serialize; use sha2::Digest; use sha2::Sha256; @@ -34,7 +37,7 @@ pub(crate) struct Schema { subgraphs: HashMap, pub(crate) implementers_map: apollo_compiler::collections::HashMap, api_schema: ApiSchema, - pub(crate) schema_id: Arc, + pub(crate) schema_id: SchemaHash, pub(crate) connectors: Option, pub(crate) launch_id: Option>, } @@ -152,7 +155,7 @@ impl Schema { let implementers_map = definitions.implementers_map(); let supergraph = Supergraph::from_schema(definitions)?; - let schema_id = Arc::new(Schema::schema_id(&raw_sdl.sdl)); + let schema_id = Schema::schema_id(&raw_sdl.sdl); let api_schema = api_schema.map(Ok).unwrap_or_else(|| { supergraph.to_api_schema(api_schema_options).map_err(|e| { @@ -186,10 +189,9 @@ impl Schema { self.supergraph.schema.schema() } - pub(crate) fn schema_id(sdl: &str) -> String { - let mut hasher = Sha256::new(); - hasher.update(sdl.as_bytes()); - format!("{:x}", hasher.finalize()) + /// Compute the Schema ID for an SDL string. + pub(crate) fn schema_id(sdl: &str) -> SchemaHash { + SchemaHash::new(sdl) } /// Extracts a string containing the entire [`Schema`]. @@ -401,6 +403,118 @@ impl std::ops::Deref for ApiSchema { } } +/// A schema ID is the sha256 hash of the schema text. +/// +/// That means that differences in whitespace and comments affect the hash, not only semantic +/// differences in the schema. +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) struct SchemaHash( + /// The internal representation is a pointer to a string. + /// This is not ideal, it might be better eg. to just have a fixed-size byte array that can be + /// turned into a string as needed. + /// But `Arc` is used in the public plugin interface and other places, so this is + /// essentially a backwards compatibility decision. + Arc, +); +impl SchemaHash { + pub(crate) fn new(sdl: &str) -> Self { + let mut hasher = Sha256::new(); + hasher.update(sdl); + let hash = format!("{:x}", hasher.finalize()); + Self(Arc::new(hash)) + } + + /// Return the underlying data. + pub(crate) fn into_inner(self) -> Arc { + self.0 + } + + /// Return the hash as a hexadecimal string slice. + pub(crate) fn as_str(&self) -> &str { + self.0.as_str() + } + + /// Compute the hash for an executable document and operation name against this schema. + /// + /// See [QueryHash] for details of what's included. + pub(crate) fn operation_hash( + &self, + query_text: &str, + operation_name: Option<&str>, + ) -> QueryHash { + QueryHash::new(self, query_text, operation_name) + } +} + +impl Display for SchemaHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.as_str()) + } +} + +/// A query hash is a unique hash for an operation from an executable document against a particular +/// schema. +/// +/// For a document with two queries A and B, queries A and B will result in a different hash even +/// if the document text is identical. +/// If query A is then executed against two different versions of the schema, the hash will be +/// different again, depending on the [SchemaHash]. +/// +/// A query hash can be obtained from a schema ID using [SchemaHash::operation_hash]. +// FIXME: rename to OperationHash since it include operation name? +#[derive(Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub(crate) struct QueryHash( + /// Unlike SchemaHash, the query hash has no backwards compatibility motivations for the internal + /// type, as it's fully private. We could consider making this a fixed-size byte array rather + /// than a Vec, but it shouldn't make a huge difference. + #[serde(with = "hex")] + Vec, +); + +impl QueryHash { + /// This constructor is not public, see [SchemaHash::operation_hash] instead. + fn new(schema_id: &SchemaHash, query_text: &str, operation_name: Option<&str>) -> Self { + let mut hasher = Sha256::new(); + hasher.update(schema_id.as_str()); + // byte separator between each part that is hashed + hasher.update(&[0xFF][..]); + hasher.update(query_text); + hasher.update(&[0xFF][..]); + hasher.update(operation_name.unwrap_or("-")); + Self(hasher.finalize().as_slice().into()) + } + + /// Return the hash as a byte slice. + pub(crate) fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl std::fmt::Debug for QueryHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("QueryHash") + .field(&hex::encode(&self.0)) + .finish() + } +} + +impl Display for QueryHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(&self.0)) + } +} + +// FIXME: It seems bad that you can create an empty hash easily and use it in security-critical +// places. This impl should be deleted outright and we should update usage sites. +// If the query hash is truly not required to contain data in those usage sites, we should use +// something like an Option instead. +#[allow(clippy::derivable_impls)] // need a place to add that comment ;) +impl Default for QueryHash { + fn default() -> Self { + Self(Default::default()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -606,7 +720,7 @@ mod tests { let schema = Schema::parse(schema, &Default::default()).unwrap(); assert_eq!( - Schema::schema_id(&schema.raw_sdl), + Schema::schema_id(&schema.raw_sdl).as_str(), "23bcf0ea13a4e0429c942bba59573ba70b8d6970d73ad00c5230d08788bb1ba2".to_string() ); } diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index 7b72dd3410..b6a7d30811 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -52,7 +52,7 @@ async fn query_planner_cache() -> Result<(), BoxError> { // If this test fails and the cache key format changed you'll need to update the key here. // Look at the top of the file for instructions on getting the new cache key. let known_cache_key = &format!( - "plan:router:{}:8c0b4bfb4630635c2b5748c260d686ddb301d164e5818c63d6d9d77e13631676:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:d9f7a00bc249cb51cfc8599f86b6dc5272967b37b1409dc4717f105b6939fe43", + "plan:router:{}:47939f0e964372951934fc662c9c2be675bc7116ec3e57029abe555284eb10a4:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:d9f7a00bc249cb51cfc8599f86b6dc5272967b37b1409dc4717f105b6939fe43", env!("CARGO_PKG_VERSION") ); @@ -453,13 +453,13 @@ async fn entity_cache_basic() -> Result<(), BoxError> { // if this is failing due to a cache key change, hook up redis-cli with the MONITOR command to see the keys being set let s:String = client - .get("version:1.0:subgraph:products:type:Query:hash:5e8ac155fe1fb5b3b69292f89b7df818a39d88a3bf77031a6bd60c22eeb4b242:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") + .get("version:1.0:subgraph:products:type:Query:hash:30cf92cd31bc204de344385c8f6d90a53da6c9180d80e8f7979a5bc19cd96055:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); let v: Value = serde_json::from_str(&s).unwrap(); insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); - let s: String = client.get("version:1.0:subgraph:reviews:type:Product:entity:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:hash:50354623eb0a347d47a62f002fae74c0f579ee693af1fdb9a1e4744b4723dd2c:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c").await.unwrap(); + let s: String = client.get("version:1.0:subgraph:reviews:type:Product:entity:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:hash:b9b8a9c94830cf56329ec2db7d7728881a6ba19cc1587710473e732e775a5870:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c").await.unwrap(); let v: Value = serde_json::from_str(&s).unwrap(); insta::assert_json_snapshot!(v.as_object().unwrap().get("data").unwrap()); @@ -571,7 +571,7 @@ async fn entity_cache_basic() -> Result<(), BoxError> { insta::assert_json_snapshot!(response); let s:String = client - .get("version:1.0:subgraph:reviews:type:Product:entity:d9a4cd73308dd13ca136390c10340823f94c335b9da198d2339c886c738abf0d:hash:50354623eb0a347d47a62f002fae74c0f579ee693af1fdb9a1e4744b4723dd2c:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") + .get("version:1.0:subgraph:reviews:type:Product:entity:d9a4cd73308dd13ca136390c10340823f94c335b9da198d2339c886c738abf0d:hash:b9b8a9c94830cf56329ec2db7d7728881a6ba19cc1587710473e732e775a5870:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); let v: Value = serde_json::from_str(&s).unwrap(); @@ -800,7 +800,7 @@ async fn entity_cache_authorization() -> Result<(), BoxError> { insta::assert_json_snapshot!(response); let s:String = client - .get("version:1.0:subgraph:products:type:Query:hash:5e8ac155fe1fb5b3b69292f89b7df818a39d88a3bf77031a6bd60c22eeb4b242:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") + .get("version:1.0:subgraph:products:type:Query:hash:30cf92cd31bc204de344385c8f6d90a53da6c9180d80e8f7979a5bc19cd96055:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); let v: Value = serde_json::from_str(&s).unwrap(); @@ -821,7 +821,7 @@ async fn entity_cache_authorization() -> Result<(), BoxError> { ); let s: String = client - .get("version:1.0:subgraph:reviews:type:Product:entity:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:hash:50354623eb0a347d47a62f002fae74c0f579ee693af1fdb9a1e4744b4723dd2c:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") + .get("version:1.0:subgraph:reviews:type:Product:entity:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:hash:b9b8a9c94830cf56329ec2db7d7728881a6ba19cc1587710473e732e775a5870:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); let v: Value = serde_json::from_str(&s).unwrap(); @@ -865,7 +865,7 @@ async fn entity_cache_authorization() -> Result<(), BoxError> { insta::assert_json_snapshot!(response); let s:String = client - .get("version:1.0:subgraph:reviews:type:Product:entity:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:hash:2253830e3b366dcfdfa4e1acf6afa9e05d3c80ff50171243768a3e416536c89b:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") + .get("version:1.0:subgraph:reviews:type:Product:entity:4911f7a9dbad8a47b8900d65547503a2f3c0359f65c0bc5652ad9b9843281f66:hash:572a2cdde770584306cd0def24555773323b1738e9a303c7abc1721b6f9f2ec4:data:d9d84a3c7ffc27b0190a671212f3740e5b8478e84e23825830e97822e25cf05c") .await .unwrap(); let v: Value = serde_json::from_str(&s).unwrap(); @@ -992,7 +992,7 @@ async fn query_planner_redis_update_query_fragments() { // This configuration turns the fragment generation option *off*. include_str!("fixtures/query_planner_redis_config_update_query_fragments.router.yaml"), &format!( - "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:fb1a8e6e454ad6a1d0d48b24dc9c7c4dd6d9bf58b6fdaf43cd24eb77fbbb3a17", + "plan:router:{}:14ece7260081620bb49f1f4934cf48510e5f16c3171181768bb46a5609d7dfb7:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:fb1a8e6e454ad6a1d0d48b24dc9c7c4dd6d9bf58b6fdaf43cd24eb77fbbb3a17", env!("CARGO_PKG_VERSION") ), ) @@ -1015,7 +1015,7 @@ async fn query_planner_redis_update_defer() { test_redis_query_plan_config_update( include_str!("fixtures/query_planner_redis_config_update_defer.router.yaml"), &format!( - "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:dc062fcc9cfd9582402d1e8b1fa3ee336ea1804d833443869e0b3744996716a2", + "plan:router:{}:14ece7260081620bb49f1f4934cf48510e5f16c3171181768bb46a5609d7dfb7:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:dc062fcc9cfd9582402d1e8b1fa3ee336ea1804d833443869e0b3744996716a2", env!("CARGO_PKG_VERSION") ), ) @@ -1040,7 +1040,7 @@ async fn query_planner_redis_update_type_conditional_fetching() { "fixtures/query_planner_redis_config_update_type_conditional_fetching.router.yaml" ), &format!( - "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:bdc09980aa6ef28a67f5aeb8759763d8ac5a4fc43afa8c5a89f58cc998c48db3", + "plan:router:{}:14ece7260081620bb49f1f4934cf48510e5f16c3171181768bb46a5609d7dfb7:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:bdc09980aa6ef28a67f5aeb8759763d8ac5a4fc43afa8c5a89f58cc998c48db3", env!("CARGO_PKG_VERSION") ), ) @@ -1068,7 +1068,7 @@ async fn test_redis_query_plan_config_update(updated_config: &str, new_cache_key // If the tests above are failing, this is the key that needs to be changed first. let starting_key = &format!( - "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:d9f7a00bc249cb51cfc8599f86b6dc5272967b37b1409dc4717f105b6939fe43", + "plan:router:{}:14ece7260081620bb49f1f4934cf48510e5f16c3171181768bb46a5609d7dfb7:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:d9f7a00bc249cb51cfc8599f86b6dc5272967b37b1409dc4717f105b6939fe43", env!("CARGO_PKG_VERSION") ); assert_ne!(starting_key, new_cache_key, "starting_key (cache key for the initial config) and new_cache_key (cache key with the updated config) should not be equal. This either means that the cache key is not being generated correctly, or that the test is not actually checking the updated key."); diff --git a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner_cache.snap b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner_cache.snap index 7f7fd862db..31853fcf98 100644 --- a/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner_cache.snap +++ b/apollo-router/tests/integration/snapshots/integration_tests__integration__redis__query_planner_cache.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/integration/redis.rs expression: query_plan +snapshot_kind: text --- { "kind": "Fetch", @@ -13,7 +14,7 @@ expression: query_plan "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "b86f4d9d705538498ec90551f9d90f9eee4386be36ad087638932dad3f44bf66", + "schemaAwareHash": "4f17a8ba2e5371d2d476e8824ffa2ecb3fc18440ad04cff216d65e33287a7cd3", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/set_context.rs b/apollo-router/tests/set_context.rs index 6ebb9e590b..990122b7da 100644 --- a/apollo-router/tests/set_context.rs +++ b/apollo-router/tests/set_context.rs @@ -313,6 +313,8 @@ fn setup_from_mocks( configuration: serde_json::Value, mocks: &[(&'static str, &'static str)], ) -> TestHarness<'static> { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let mut mocked_subgraphs = MockedSubgraphs::default(); for (name, m) in mocks { diff --git a/apollo-router/tests/snapshots/set_context__set_context.snap b/apollo-router/tests/snapshots/set_context__set_context.snap index 3630f5d86a..264f37cdb6 100644 --- a/apollo-router/tests/snapshots/set_context__set_context.snap +++ b/apollo-router/tests/snapshots/set_context__set_context.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -34,7 +35,7 @@ expression: response "operationKind": "query", "operationName": "Query__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "b45f75d11c91f90d616e0786fe9a1a675f4f478a6688aa38b9809b3416b66507", + "schemaAwareHash": "c36f4148cc7bdd9d001316592ffaa2c0079f93586fb1ca110aa5107262afdb44", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -80,7 +81,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "02dbfc4ce65b1eb8ee39c37f09a88b56ee4671bbcdc935f3ec2a7e25e36c2931", + "schemaAwareHash": "6d7e81f6b6fb544929a7f5fbc9123b6a5ad49687dbd4528d2c5b91d0706ded2f", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap b/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap index c0c964b497..92771a71ae 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": null, @@ -43,7 +44,7 @@ expression: response "operationKind": "query", "operationName": "Query_fetch_dependent_failure__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "e8671657b38c13454f18f2bf8df9ebbeb80235c50592f72a2c4141803fe6db59", + "schemaAwareHash": "d93d363a607457b0fab08dcc177dadd9f647e2ce2c0e893017c99b9e0a452d15", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -89,7 +90,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "8499a69f5ac2e4ce2e0acc76b38b7839b89b6ccba9142494d1a82dd17dd0e5f2", + "schemaAwareHash": "1e00dfec6c7e1f482d9615cc2d530e79d2503f8527e2f3ee60a1b5b462088d5d", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure_rust_qp.snap index 8e555063f8..8921d12e90 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_dependent_fetch_failure_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": null, @@ -43,7 +44,7 @@ expression: response "operationKind": "query", "operationName": "Query_fetch_dependent_failure__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "dfb0f7a17a089f11d0c95f8e9acb3a17fa4fb21216843913bc3a6c62ce2b7fbd", + "schemaAwareHash": "f4657a18062d1238f3b2c70313ccc4033382f1ab564bdb17e9c23b4f301502eb", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -89,7 +90,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "f5ae7b50fe8d94eedfb385d91e561de06e3a3256fedca901c0b50ae689b5d630", + "schemaAwareHash": "a474ca499374743ca5ecf861b75aa195796cf573af48a7f948f8652fecb38981", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_list.snap b/apollo-router/tests/snapshots/set_context__set_context_list.snap index fa0a57fc15..d1111153fb 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_list.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_list.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -40,7 +41,7 @@ expression: response "operationKind": "query", "operationName": "Query__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "50ba3d7291f38802f222251fe79055e06345e62252e74eba9e01bbec34510cea", + "schemaAwareHash": "fae8d004c9012c52e0eaf36b650afe513a58164f3b7c6721a92194dc2594e222", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -86,7 +87,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "02dbfc4ce65b1eb8ee39c37f09a88b56ee4671bbcdc935f3ec2a7e25e36c2931", + "schemaAwareHash": "6d7e81f6b6fb544929a7f5fbc9123b6a5ad49687dbd4528d2c5b91d0706ded2f", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap b/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap index 13e0282397..e089bf37c3 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_list_of_lists.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -44,7 +45,7 @@ expression: response "operationKind": "query", "operationName": "QueryLL__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "589a7dec7f09fdedd06128f1e7396c727740ac1f84ad936ea9c61c3cf96d3ee4", + "schemaAwareHash": "1623e5c5abcc506f378e168da719f9163a03ee62feba9413b5664c8d6b5f1b37", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -90,7 +91,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "0c966292093d13acca6c8ebb257a146a46840e5a04c9cbaede12e08df98cd489", + "schemaAwareHash": "9f32284e712d75def9f4f7d28f563ff31101b9e6492cef5158b9b10b3a389cd2", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_list_of_lists_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_list_of_lists_rust_qp.snap index 6dabc52d4a..ca3c9030bd 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_list_of_lists_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_list_of_lists_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -44,7 +45,7 @@ expression: response "operationKind": "query", "operationName": "QueryLL__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "560ba34c3cdda6c435aaab55e21528b252f44caabc6c082117e4e9fcc935af5f", + "schemaAwareHash": "faf129da6ef159312229575dba91b32e5218486fbb568126314cadeaab16dfff", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -90,7 +91,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "b97924736c4f71e4b6e80e2a9e2661130363820bd3df5b2e38000be4a4fb47b5", + "schemaAwareHash": "23d14a2cd64a8ae9af87935761752a6b6e54e376bc5bcad977c8d8bc7fae0569", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_list_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_list_rust_qp.snap index 2cee07ad33..8dd2359bb9 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_list_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_list_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -40,7 +41,7 @@ expression: response "operationKind": "query", "operationName": "set_context_list_rust_qp__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "f89e82a2730898d4c37766615534224fe8f569b4786a3946e652572a1b99117d", + "schemaAwareHash": "4acf5d3b86cc26a35aec8502ed93a957743d2f614f0b2c2af410caddb7a5a1d7", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -86,7 +87,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "57d42b319499942de11c8eaf8bedb3618079a21fb01792b1a0a1ca8a1157d04c", + "schemaAwareHash": "d5b8a4e67c6af63de00246c793278373735149a7f92b9b9b85fdf640e046af75", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap b/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap index 0b45046ad7..40018ffb66 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_no_typenames.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -32,7 +33,7 @@ expression: response "operationKind": "query", "operationName": "Query__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "b45f75d11c91f90d616e0786fe9a1a675f4f478a6688aa38b9809b3416b66507", + "schemaAwareHash": "c36f4148cc7bdd9d001316592ffaa2c0079f93586fb1ca110aa5107262afdb44", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -78,7 +79,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "02dbfc4ce65b1eb8ee39c37f09a88b56ee4671bbcdc935f3ec2a7e25e36c2931", + "schemaAwareHash": "6d7e81f6b6fb544929a7f5fbc9123b6a5ad49687dbd4528d2c5b91d0706ded2f", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_no_typenames_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_no_typenames_rust_qp.snap index d110b1b332..12fbe3d503 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_no_typenames_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_no_typenames_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -32,7 +33,7 @@ expression: response "operationKind": "query", "operationName": "set_context_no_typenames_rust_qp__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "9b5e7d0de84a6e670d5235682e776eb0ebcd753c955403c7159adea338813a93", + "schemaAwareHash": "025987a7a8c866dd913dc56c4dc6f3285bcd34035857ef0224ef6bebdc6b944b", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -78,7 +79,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "0b5a9920448d114be6250a0b85f3092f5dfbce80dc89e26880fb28a5ea684d3b", + "schemaAwareHash": "ec32c05f19b6267dc408ac378410f75ad80e8dd4c62cacadea309aed04375958", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_rust_qp.snap index 7e7088d73f..9154fbf1b6 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -34,7 +35,7 @@ expression: response "operationKind": "query", "operationName": "set_context_rust_qp__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "d9094fb75802583731265ab088bd914c2c10ad3d2f7e835cbe13d58811ab797f", + "schemaAwareHash": "7b4176cae048d89760954a60ec441f2ee2508a5ac1eb4a6c123fc5cf8a930061", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -80,7 +81,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "971f94cb09cb7a1a5564661ae4345da5e709f3ae16b81215db80ae61a740e8d2", + "schemaAwareHash": "8fe3ef0b5c98418fd5201a62a97275c32e7177bb2e6a62dcc876d65043cbc647", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap b/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap index 2c9c27bc14..c985004f1b 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_type_mismatch.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -32,7 +33,7 @@ expression: response "operationKind": "query", "operationName": "Query_type_mismatch__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "f47b7620f3ba24d2c15a2978451bd7b59f462e63dc3259a244efe1d971979bfa", + "schemaAwareHash": "76744b6c5c054056f7aa9ebdd8ee4f64ee6534e49ededa336b7dae377d1224bc", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -78,7 +79,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "a6ff3cddbf800b647fdb15f6da6d5e68a71979be93d51852bd289f047202d8ac", + "schemaAwareHash": "d420e2fba88b355380ea18a55bc27b3aff8d776bd03d04d5f44e19402a4eff10", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_type_mismatch_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_type_mismatch_rust_qp.snap index abacde1e41..c27db78ff9 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_type_mismatch_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_type_mismatch_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -32,7 +33,7 @@ expression: response "operationKind": "query", "operationName": "Query_type_mismatch__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "0ca90df94a895d97f403b550a05e72808aee80cbfc6f2f3aea8d32ae0d73d2cd", + "schemaAwareHash": "bc701fd1cb3c50c87798d10c72214eee3997b27ad8bd4d3c0c3a6dd4941c354d", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -78,7 +79,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "8ea1a4dce3d934c98814d56884f9e7dad9045562a072216ea903570e01c04680", + "schemaAwareHash": "e23a1e560864aa0265191937008bdeaa05806cd33f10f63f4ee2f58457103c24", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_union.snap b/apollo-router/tests/snapshots/set_context__set_context_union.snap index 50c3e865df..ce3c434108 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_union.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_union.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -31,7 +32,7 @@ expression: response "operationKind": "query", "operationName": "QueryUnion__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "b6ed60b7e69ed10f45f85aba713969cd99b0e1a832464ba3f225fdf055706424", + "schemaAwareHash": "5ca91ce52f6337db88ab61d494f5b3b52b37a4a37bf9efb386cec134e86d4660", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -80,7 +81,7 @@ expression: response "typeCondition": "V" } ], - "schemaAwareHash": "99faa73249f207ea11b1b5064d77f278367398cfee881b2fc3a8a9ebe53f44fe", + "schemaAwareHash": "bd757755f6a3cb0116e3a27612807f80a9ea376e3a8fe7bb2bff9c94290953dd", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_1" @@ -134,7 +135,7 @@ expression: response "typeCondition": "V" } ], - "schemaAwareHash": "e925299b31ea9338d10257fd150ec7ece230f55117105dd631181f4b2a33075a", + "schemaAwareHash": "f4c7f9d3ce28970fcb8676c9808fa1609e2d88643bf9c43804311ecb4a4001e1", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_1" diff --git a/apollo-router/tests/snapshots/set_context__set_context_union_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_union_rust_qp.snap index c19a7222e8..96098ca158 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_union_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_union_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -31,7 +32,7 @@ expression: response "operationKind": "query", "operationName": "QueryUnion__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "967a248e156212f72f5abb27c01fe3d5e8bb4db154e0f7015db551ee0fe46877", + "schemaAwareHash": "9ac5ac3e8e654a2bdcc70f655e4909b9cba143d055865f75da27f51f5d4fdea4", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -80,7 +81,7 @@ expression: response "typeCondition": "V" } ], - "schemaAwareHash": "77647255b7cbdfcb4a47ab2492c0c5252c4f6d06dd4008b104d8770a584a1e32", + "schemaAwareHash": "79ecb3f9d1c195dceba17f3bf0198fad3c25a6a05e7181e07e7b9565a40d4127", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_1" @@ -133,7 +134,7 @@ expression: response "typeCondition": "V" } ], - "schemaAwareHash": "4a608fbd27c2498c1f31bf737143990d8a2f31e72682542d3169fe2fac6d5834", + "schemaAwareHash": "7418a461d65b6d0663c8cb55a22d2f84f8abcb5dd55cee5384323bf85e700ac9", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_1" diff --git a/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap b/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap index b80bb0c853..868b7de485 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": null, @@ -37,7 +38,7 @@ expression: response "operationKind": "query", "operationName": "Query_fetch_failure__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "d321568b33e32986df6d30a82443ebb919949617ffc33affe8b413658af52b8a", + "schemaAwareHash": "d6133b40599aaa7350d8c321d5313a7823d3430caab4c250754d24bb88ad0bce", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -76,7 +77,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "a9aa68bb30f2040298629fc2fe72dc8438ce16bcdfdbe1a16ff088cf61d38719", + "schemaAwareHash": "690a46547feb85f93bad65bf9c7015add2403adb114442971001b437549e45db", "serviceName": "Subgraph2", "variableUsages": [] }, @@ -128,7 +129,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "da0e31f9990723a68dbd1e1bb164a068342da5561db1a28679693a406429d09a", + "schemaAwareHash": "5281c0f7b42b131f5e9a629d3340eac6026e5042580283b0f3d1b5892b351a22", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" diff --git a/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure_rust_qp.snap index 77341c5367..a03ce09f0b 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_unrelated_fetch_failure_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": null, @@ -37,7 +38,7 @@ expression: response "operationKind": "query", "operationName": "Query_fetch_failure__Subgraph1__0", "outputRewrites": null, - "schemaAwareHash": "03786f26d73a1ad1bfa3fed40f657316857018dc1105b2da578904373b7e1882", + "schemaAwareHash": "69aefb1c00e6c759f7238b7f4388bf0b5b8351cecf236e59a385165ccd4de986", "serviceName": "Subgraph1", "variableUsages": [] }, @@ -86,7 +87,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "f615a413abdf99efaf7e760e1246371aa5dd0f2330820cf295335ed48cc077ed", + "schemaAwareHash": "a2d9d5ae511effd9280bdf0cde021c36206e6e578e1d6722a74584cdec287f8b", "serviceName": "Subgraph1", "variableUsages": [ "contextualArgument_1_0" @@ -129,7 +130,7 @@ expression: response "typeCondition": "U" } ], - "schemaAwareHash": "0e71b293b5c0b6143252865b2c97338cd72a558897b0a478dd0cd8b027f9a5a3", + "schemaAwareHash": "eda16e56540e88943000a1936786574f5da632ea4d4ab73b49cea306db8f9b25", "serviceName": "Subgraph2", "variableUsages": [] }, diff --git a/apollo-router/tests/snapshots/set_context__set_context_with_null.snap b/apollo-router/tests/snapshots/set_context__set_context_with_null.snap index f4c5eb3898..6c03832503 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_with_null.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_with_null.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -29,7 +30,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "73819a48542fc2a3eb3a831b27ab5cc0b1286a73c2750279d8886fc529ba9e9e", + "schemaAwareHash": "72f6c5c3c41936eba859c4327265e74c9421e41d684ce560f3cc5c0a1bef201f", "authorization": { "is_authenticated": false, "scopes": [], @@ -82,7 +83,7 @@ expression: response "renameKeyTo": "contextualArgument_1_0" } ], - "schemaAwareHash": "042955e454618e67e75f3c86c9b8c71e2da866f1c40d0dc462d52053e1861803", + "schemaAwareHash": "2d6f86d4ed32670400197090358c53094c0f83892f3016ac1d067663d215b83a", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/set_context__set_context_with_null_rust_qp.snap b/apollo-router/tests/snapshots/set_context__set_context_with_null_rust_qp.snap index fcd539eeb1..0527808fc7 100644 --- a/apollo-router/tests/snapshots/set_context__set_context_with_null_rust_qp.snap +++ b/apollo-router/tests/snapshots/set_context__set_context_with_null_rust_qp.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/set_context.rs expression: response +snapshot_kind: text --- { "data": { @@ -29,7 +30,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "5a33cc9574d930882310fe1f9ddae8f262a448a50ac9a899e71896a339fa0f85", + "schemaAwareHash": "3a42fbf2a9a7f48e3ed94f72391fef2af7a06310b2ac92e4ee76c2ceb24507fb", "authorization": { "is_authenticated": false, "scopes": [], @@ -81,7 +82,7 @@ expression: response "renameKeyTo": "contextualArgument_1_0" } ], - "schemaAwareHash": "4f6eeca0e601bbf183b759aa785df84eb0c435a266a36568238e8d721dc8fc3c", + "schemaAwareHash": "72ee0ea9ed33e8ce0736bb392a654d4cf6f9316809be1e9b0aed00842b6e37a9", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled-2.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled-2.snap index 4d61ae95b4..a0994540d9 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled-2.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -79,7 +80,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", + "schemaAwareHash": "d406d6b0a5762bf096ec0e86318ae6485042b9bc7190417330783148728e9ed3", "authorization": { "is_authenticated": false, "scopes": [], @@ -137,7 +138,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "66a2bd39c499f1edd8c3ec1bfbc170cb995c6f9e23427b5486b633decd2da08b", + "schemaAwareHash": "6ec77a0c610f95d5709448368fcaeaf82cff4d5264213c90a9bea6648aa4fb91", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled.snap index 4d61ae95b4..a0994540d9 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_disabled.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -79,7 +80,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", + "schemaAwareHash": "d406d6b0a5762bf096ec0e86318ae6485042b9bc7190417330783148728e9ed3", "authorization": { "is_authenticated": false, "scopes": [], @@ -137,7 +138,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "66a2bd39c499f1edd8c3ec1bfbc170cb995c6f9e23427b5486b633decd2da08b", + "schemaAwareHash": "6ec77a0c610f95d5709448368fcaeaf82cff4d5264213c90a9bea6648aa4fb91", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled-2.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled-2.snap index c89cab9c7d..31d8866704 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled-2.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -79,7 +80,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", + "schemaAwareHash": "d406d6b0a5762bf096ec0e86318ae6485042b9bc7190417330783148728e9ed3", "authorization": { "is_authenticated": false, "scopes": [], @@ -140,7 +141,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -199,7 +200,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled.snap index c89cab9c7d..31d8866704 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -79,7 +80,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", + "schemaAwareHash": "d406d6b0a5762bf096ec0e86318ae6485042b9bc7190417330783148728e9ed3", "authorization": { "is_authenticated": false, "scopes": [], @@ -140,7 +141,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -199,7 +200,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments-2.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments-2.snap index c89cab9c7d..31d8866704 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments-2.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -79,7 +80,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", + "schemaAwareHash": "d406d6b0a5762bf096ec0e86318ae6485042b9bc7190417330783148728e9ed3", "authorization": { "is_authenticated": false, "scopes": [], @@ -140,7 +141,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -199,7 +200,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments.snap index c89cab9c7d..31d8866704 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_generate_query_fragments.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -79,7 +80,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", + "schemaAwareHash": "d406d6b0a5762bf096ec0e86318ae6485042b9bc7190417330783148728e9ed3", "authorization": { "is_authenticated": false, "scopes": [], @@ -140,7 +141,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -199,7 +200,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list-2.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list-2.snap index 371fd3496e..2258fe8e2d 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list-2.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -141,7 +142,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "b74616ae898acf3abefb83e24bde5faf0de0f9475d703b105b60c18c7372ab13", + "schemaAwareHash": "1632c390b36440b872e929531433045c45d543b28ee5452c8e2ed29e989216a5", "authorization": { "is_authenticated": false, "scopes": [], @@ -203,7 +204,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -263,7 +264,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list.snap index 371fd3496e..2258fe8e2d 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -141,7 +142,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "b74616ae898acf3abefb83e24bde5faf0de0f9475d703b105b60c18c7372ab13", + "schemaAwareHash": "1632c390b36440b872e929531433045c45d543b28ee5452c8e2ed29e989216a5", "authorization": { "is_authenticated": false, "scopes": [], @@ -203,7 +204,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -263,7 +264,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list-2.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list-2.snap index 354bd034a9..b8561c29af 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list-2.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -145,7 +146,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "dc1df8e8d701876c6ea7d25bbeab92a5629a82e55660ccc48fc37e12d5157efa", + "schemaAwareHash": "a6412469787fe50d6d640f808950d84a1fbd4f4599d8ad2d2b50bee125f032cf", "authorization": { "is_authenticated": false, "scopes": [], @@ -208,7 +209,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -269,7 +270,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list.snap index 354bd034a9..b8561c29af 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_list_of_list_of_list.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -145,7 +146,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "dc1df8e8d701876c6ea7d25bbeab92a5629a82e55660ccc48fc37e12d5157efa", + "schemaAwareHash": "a6412469787fe50d6d640f808950d84a1fbd4f4599d8ad2d2b50bee125f032cf", "authorization": { "is_authenticated": false, "scopes": [], @@ -208,7 +209,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", + "schemaAwareHash": "ca74ee896977e09e7467fdc5a03698e748c88209df8c84f85293bd63cb4afa8b", "authorization": { "is_authenticated": false, "scopes": [], @@ -269,7 +270,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", + "schemaAwareHash": "692bb14128b6c838ddc7223c195fd6c47ef867685f195e88002b918d11be065e", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch-2.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch-2.snap index 8811454f74..f89cde6b32 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch-2.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch-2.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -54,7 +55,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "446c1a72168f736a89e4f56799333e05b26092d36fc55e22c2e92828061c787b", + "schemaAwareHash": "3f0b3f09181fe1a524e02ea7ae290aac97f6c5583cbde41c7073dab6af79690a", "authorization": { "is_authenticated": false, "scopes": [], @@ -115,7 +116,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "f9052a9ce97a084006a1f2054b7e0fba8734f24bb53cf0f7e0ba573c7e709b98", + "schemaAwareHash": "5b3ed44a13782c7a73009a76a6906a407c57538d4c26a6251b4d4b1253730923", "authorization": { "is_authenticated": false, "scopes": [], @@ -174,7 +175,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "027cac0584184439636aea68757da18f3e0e18142948e3b8625724f93e8720fc", + "schemaAwareHash": "0cb0308f000c80067924c40cb212dd2323fd2081621bf557bf87354c2a9b6a06", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch.snap b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch.snap index 8811454f74..f89cde6b32 100644 --- a/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch.snap +++ b/apollo-router/tests/snapshots/type_conditions___test_type_conditions_enabled_shouldnt_make_article_fetch.snap @@ -1,6 +1,7 @@ --- source: apollo-router/tests/type_conditions.rs expression: response +snapshot_kind: text --- { "data": { @@ -54,7 +55,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "446c1a72168f736a89e4f56799333e05b26092d36fc55e22c2e92828061c787b", + "schemaAwareHash": "3f0b3f09181fe1a524e02ea7ae290aac97f6c5583cbde41c7073dab6af79690a", "authorization": { "is_authenticated": false, "scopes": [], @@ -115,7 +116,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "f9052a9ce97a084006a1f2054b7e0fba8734f24bb53cf0f7e0ba573c7e709b98", + "schemaAwareHash": "5b3ed44a13782c7a73009a76a6906a407c57538d4c26a6251b4d4b1253730923", "authorization": { "is_authenticated": false, "scopes": [], @@ -174,7 +175,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "027cac0584184439636aea68757da18f3e0e18142948e3b8625724f93e8720fc", + "schemaAwareHash": "0cb0308f000c80067924c40cb212dd2323fd2081621bf557bf87354c2a9b6a06", "authorization": { "is_authenticated": false, "scopes": [], diff --git a/apollo-router/tests/type_conditions.rs b/apollo-router/tests/type_conditions.rs index 09466bee5d..4104a0cab3 100644 --- a/apollo-router/tests/type_conditions.rs +++ b/apollo-router/tests/type_conditions.rs @@ -354,6 +354,8 @@ fn setup_from_mocks( configuration: serde_json::Value, mocks: &[(&'static str, &'static str)], ) -> TestHarness<'static> { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + let mut mocked_subgraphs = MockedSubgraphs::default(); for (name, m) in mocks {