diff --git a/.changesets/maint_simon_unbridge.md b/.changesets/maint_simon_unbridge.md new file mode 100644 index 0000000000..6beb0908d3 --- /dev/null +++ b/.changesets/maint_simon_unbridge.md @@ -0,0 +1,11 @@ +### Remove the legacy query planner ([PR #6418](https://github.com/apollographql/router/pull/6418)) + +The legacy query planner has been removed in this release. In the previous release, Router 1.58, it was already no longer used by default but it was still available through the `experimental_query_planner_mode` configuration key. That key is now removed. + +Also removed are configuration keys which were only relevant to the legacy planner: + +* `supergraph.query_planning.experimental_parallelism`: the new planner can always use available parallelism. +* `supergraph.experimental_reuse_query_fragments`: this experimental algorithm that attempted to +reuse fragments from the original operation while forming subgraph requests is no longer present. Instead, by default new fragment definitions are generated based on the shape of the subgraph operation. + +By [@SimonSapin](https://github.com/SimonSapin) in https://github.com/apollographql/router/pull/6418 diff --git a/.config/nextest.toml b/.config/nextest.toml index f2c4ef3618..5c2a479dc3 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -118,7 +118,6 @@ or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::en or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::query_planner_redis_update_defer) ) or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::query_planner_redis_update_introspection) ) or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::query_planner_redis_update_query_fragments) ) -or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::query_planner_redis_update_reuse_query_fragments) ) or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::query_planner_redis_update_type_conditional_fetching) ) or ( binary_id(=apollo-router::integration_tests) & test(=integration::redis::test::connection_failure_blocks_startup) ) or ( binary_id(=apollo-router::integration_tests) & test(=integration::subgraph_response::test_invalid_error_locations_contains_negative_one_location) ) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31ee69219e..c0e0f8af53 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,8 +51,6 @@ Refer to [the README file](README.md) or run `cargo run --help` for more informa - `crates/apollo-router/src/main.rs`: the entry point for the executable -Some of the functionalities rely on the current Javascript / TypeScript implementation, provided by [apollo federation](https://github.com/apollographql/federation), which is exposed through the [federation router-bridge](https://github.com/apollographql/federation/tree/main/router-bridge). - ## Documentation Documentation for using and contributing to the Apollo Router Core is built using Gatsby diff --git a/Cargo.lock b/Cargo.lock index b6258b95be..5805ff09d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,7 +365,6 @@ dependencies = [ "reqwest", "rhai", "rmp", - "router-bridge", "rowan", "rstack", "rust-embed", @@ -374,7 +373,7 @@ dependencies = [ "rustls-pemfile", "ryu", "schemars", - "semver 1.0.23", + "semver", "serde", "serde_derive_default", "serde_json", @@ -1186,7 +1185,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "rustc_version 0.4.0", + "rustc_version", "tracing", ] @@ -1636,15 +1635,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", -] - [[package]] name = "colorchoice" version = "1.0.1" @@ -2020,132 +2010,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "serde", - "uuid", -] - -[[package]] -name = "deno-proc-macro-rules" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c65c2ffdafc1564565200967edc4851c7b55422d3913466688907efd05ea26f" -dependencies = [ - "deno-proc-macro-rules-macros", - "proc-macro2", - "syn 2.0.90", -] - -[[package]] -name = "deno-proc-macro-rules-macros" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3047b312b7451e3190865713a4dd6e1f821aed614ada219766ebc3024a690435" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "deno_console" -version = "0.115.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ab05b798826985966deb29fc6773ed29570de2f2147a30c4289c7cdf635214" -dependencies = [ - "deno_core", -] - -[[package]] -name = "deno_core" -version = "0.200.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ba264b90ceb6e95b39d82e674d8ecae86ca012f900338ea50d1a077d9d75fd" -dependencies = [ - "anyhow", - "bytes", - "deno_ops", - "futures", - "indexmap 1.9.3", - "libc", - "log", - "once_cell", - "parking_lot", - "pin-project", - "serde", - "serde_json", - "serde_v8", - "smallvec", - "sourcemap", - "tokio", - "url", - "v8", -] - -[[package]] -name = "deno_ops" -version = "0.78.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd1c83b1fd465ee0156f2917c9af9ca09fe2bf54052a2cae1a8dcbc7b89aefc" -dependencies = [ - "deno-proc-macro-rules", - "lazy-regex", - "once_cell", - "pmutil", - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "strum 0.25.0", - "strum_macros 0.25.3", - "syn 1.0.109", - "syn 2.0.90", - "thiserror", -] - -[[package]] -name = "deno_url" -version = "0.115.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20490fff3b0f8c176a815e26371ff23313ea7f39cd51057701524c5b6fc36f6c" -dependencies = [ - "deno_core", - "serde", - "urlpattern", -] - -[[package]] -name = "deno_web" -version = "0.146.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc8dda6e1337d4739ae9e94d75521689824d82a7deb154a2972b6eedac64507" -dependencies = [ - "async-trait", - "base64-simd", - "deno_core", - "encoding_rs", - "flate2", - "serde", - "tokio", - "uuid", - "windows-sys 0.48.0", -] - -[[package]] -name = "deno_webidl" -version = "0.115.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73159d81053ead02e938b46d4bb7224c8e7cf25273ac16a250fb45bb09af7635" -dependencies = [ - "deno_core", -] - [[package]] name = "der" version = "0.7.9" @@ -2198,7 +2062,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 2.0.90", ] @@ -2601,7 +2465,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", - "libz-ng-sys", "miniz_oxide", ] @@ -2724,7 +2587,7 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-webpki", - "semver 1.0.23", + "semver", "socket2 0.5.7", "tokio", "tokio-rustls", @@ -2743,16 +2606,6 @@ dependencies = [ "dunce", ] -[[package]] -name = "fslock" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57eafdd0c16f57161105ae1b98a1238f97645f2f588438b2949c99a2af9616bf" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futures" version = "0.3.30" @@ -3665,12 +3518,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if_chain" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" - [[package]] name = "indexmap" version = "1.9.3" @@ -3985,29 +3832,6 @@ dependencies = [ "log", ] -[[package]] -name = "lazy-regex" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff63c423c68ea6814b7da9e88ce585f793c87ddd9e78f646970891769c8235d4" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edfc11b8f56ce85e207e62ea21557cfa09bb24a8f6b04ae181b086ff8611c22" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -4081,16 +3905,6 @@ dependencies = [ "threadpool", ] -[[package]] -name = "libz-ng-sys" -version = "1.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5" -dependencies = [ - "cmake", - "libc", -] - [[package]] name = "libz-sys" version = "1.1.18" @@ -4443,7 +4257,6 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand 0.8.5", ] [[package]] @@ -5104,17 +4917,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "pmutil" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - [[package]] name = "polling" version = "2.8.0" @@ -5762,30 +5564,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "router-bridge" -version = "0.6.4+v2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bcc6f2aa0c619a4fb74ce271873a500f5640c257ca2e7aa8ea6be6226262855" -dependencies = [ - "anyhow", - "async-channel 1.9.0", - "deno_console", - "deno_core", - "deno_url", - "deno_web", - "deno_webidl", - "rand 0.8.5", - "serde", - "serde_json", - "thiserror", - "tokio", - "tower", - "tower-service", - "tracing", - "which", -] - [[package]] name = "router-fuzz" version = "0.0.0" @@ -5880,22 +5658,13 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.23", + "semver", ] [[package]] @@ -6091,27 +5860,12 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.215" @@ -6121,15 +5875,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_bytes" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" -dependencies = [ - "serde", -] - [[package]] name = "serde_derive" version = "1.0.215" @@ -6233,22 +5978,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_v8" -version = "0.111.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309b3060a9627882514f3a3ce3cc08ceb347a76aeeadc58f138c3f189cf88b71" -dependencies = [ - "bytes", - "derive_more", - "num-bigint", - "serde", - "serde_bytes", - "smallvec", - "thiserror", - "v8", -] - [[package]] name = "serde_yaml" version = "0.8.26" @@ -6425,22 +6154,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "sourcemap" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4cbf65ca7dc576cf50e21f8d0712d96d4fcfd797389744b7b222a85cdf5bd90" -dependencies = [ - "data-encoding", - "debugid", - "if_chain", - "rustc_version 0.2.3", - "serde", - "serde_json", - "unicode-id", - "url", -] - [[package]] name = "spin" version = "0.9.8" @@ -7408,47 +7121,6 @@ dependencies = [ "libc", ] -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.7.0" @@ -7464,12 +7136,6 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" -[[package]] -name = "unicode-id" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -7524,19 +7190,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "urlpattern" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609" -dependencies = [ - "derive_more", - "regex", - "serde", - "unic-ucd-ident", - "url", -] - [[package]] name = "utf-8" version = "0.7.6" @@ -7572,18 +7225,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "v8" -version = "0.74.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eedac634b8dd39b889c5b62349cbc55913780226239166435c5cf66771792ea" -dependencies = [ - "bitflags 1.3.2", - "fslock", - "once_cell", - "which", -] - [[package]] name = "valuable" version = "0.1.0" diff --git a/apollo-router/Cargo.toml b/apollo-router/Cargo.toml index b2b139e9d7..890d9feb25 100644 --- a/apollo-router/Cargo.toml +++ b/apollo-router/Cargo.toml @@ -44,10 +44,6 @@ dhat-ad-hoc = ["dhat"] # alerted early when something is wrong instead of receiving an invalid result. failfast = [] -# "fake" feature to disable V8 usage when building on docs.rs -# See https://github.com/apollographql/federation-rs/pull/185 -docs_rs = ["router-bridge/docs_rs"] - # Enables the use of new telemetry features that are under development # and not yet ready for production use. telemetry_next = [] @@ -59,9 +55,6 @@ hyper_header_limits = [] # is set when ci builds take place. It allows us to disable some tests when CI is running on certain platforms. ci = [] -[package.metadata.docs.rs] -features = ["docs_rs"] - [dependencies] access-json = "0.1.0" anyhow = "1.0.86" @@ -191,10 +184,6 @@ rand = "0.8.5" rhai = { version = "1.19.0", features = ["sync", "serde", "internals"] } regex = "1.10.5" reqwest.workspace = true - -# note: this dependency should _always_ be pinned, prefix the version with an `=` -router-bridge = "=0.6.4+v2.9.3" - rust-embed = { version = "8.4.0", features = ["include-exclude"] } rustls = "0.21.12" rustls-native-certs = "0.6.3" diff --git a/apollo-router/examples/router.yaml b/apollo-router/examples/router.yaml index 6936d57e32..b544aab718 100644 --- a/apollo-router/examples/router.yaml +++ b/apollo-router/examples/router.yaml @@ -1,8 +1,6 @@ supergraph: listen: 0.0.0.0:4100 introspection: true - query_planning: - experimental_parallelism: auto # or any number plugins: experimental.expose_query_plan: true apollo-test.do_not_execute: true diff --git a/apollo-router/src/apollo_studio_interop/mod.rs b/apollo-router/src/apollo_studio_interop/mod.rs index 4499a9b557..b9877374ea 100644 --- a/apollo-router/src/apollo_studio_interop/mod.rs +++ b/apollo-router/src/apollo_studio_interop/mod.rs @@ -26,8 +26,7 @@ use apollo_compiler::ExecutableDocument; use apollo_compiler::Name; use apollo_compiler::Node; use apollo_compiler::Schema; -use router_bridge::planner::ReferencedFieldsForType; -use router_bridge::planner::UsageReporting; +use serde::Deserialize; use serde::Serialize; use crate::json_ext::Object; @@ -162,14 +161,32 @@ impl AddAssign for AggregatedExtendedReferenceStats { } } -/// The result of the generate_usage_reporting function which contains a UsageReporting struct and -/// functions that allow comparison with another ComparableUsageReporting or UsageReporting object. -pub(crate) struct ComparableUsageReporting { - /// The UsageReporting fields - pub(crate) result: UsageReporting, +/// UsageReporting fields, that will be used to send stats to uplink/studio +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub(crate) struct UsageReporting { + /// The `stats_report_key` is a unique identifier derived from schema and query. + /// Metric data sent to Studio must be aggregated + /// via grouped key of (`client_name`, `client_version`, `stats_report_key`). + pub(crate) stats_report_key: String, + /// a list of all types and fields referenced in the query + #[serde(default)] + pub(crate) referenced_fields_by_type: HashMap, } -/// Generate a ComparableUsageReporting containing the stats_report_key (a normalized version of the operation signature) +/// A list of fields that will be resolved for a given type +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ReferencedFieldsForType { + /// names of the fields queried + #[serde(default)] + pub(crate) field_names: Vec, + /// whether the field is an interface + #[serde(default)] + pub(crate) is_interface: bool, +} + +/// Generate a UsageReporting containing the stats_report_key (a normalized version of the operation signature) /// and referenced fields of an operation. The document used to generate the signature and for the references can be /// different to handle cases where the operation has been filtered, but we want to keep the same signature. pub(crate) fn generate_usage_reporting( @@ -178,7 +195,7 @@ pub(crate) fn generate_usage_reporting( operation_name: &Option, schema: &Valid, normalization_algorithm: &ApolloSignatureNormalizationAlgorithm, -) -> ComparableUsageReporting { +) -> UsageReporting { let mut generator = UsageGenerator { signature_doc, references_doc, @@ -343,12 +360,10 @@ struct UsageGenerator<'a> { } impl UsageGenerator<'_> { - fn generate_usage_reporting(&mut self) -> ComparableUsageReporting { - ComparableUsageReporting { - result: UsageReporting { - stats_report_key: self.generate_stats_report_key(), - referenced_fields_by_type: self.generate_apollo_reporting_refs(), - }, + fn generate_usage_reporting(&mut self) -> UsageReporting { + UsageReporting { + stats_report_key: self.generate_stats_report_key(), + referenced_fields_by_type: self.generate_apollo_reporting_refs(), } } diff --git a/apollo-router/src/apollo_studio_interop/tests.rs b/apollo-router/src/apollo_studio_interop/tests.rs index 754951983c..1dd4d0fd89 100644 --- a/apollo-router/src/apollo_studio_interop/tests.rs +++ b/apollo-router/src/apollo_studio_interop/tests.rs @@ -1,54 +1,11 @@ use apollo_compiler::Schema; -use router_bridge::planner::PlanOptions; -use router_bridge::planner::Planner; -use router_bridge::planner::QueryPlannerConfig; use test_log::test; use super::*; use crate::Configuration; -macro_rules! assert_generated_report { - ($actual:expr) => { - // Field names need sorting - let mut result = $actual.result; - for ty in result.referenced_fields_by_type.values_mut() { - ty.field_names.sort(); - } - - insta::with_settings!({sort_maps => true, snapshot_suffix => "report"}, { - insta::assert_yaml_snapshot!(result); - }); - }; -} - -// Generate the signature and referenced fields using router-bridge to confirm that the expected value we used is correct. -// We can remove this when we no longer use the bridge but should keep the rust implementation verifications. -macro_rules! assert_bridge_results { - ($schema_str:expr, $query_str:expr) => { - let planner = Planner::::new( - $schema_str.to_string(), - QueryPlannerConfig::default(), - ) - .await - .unwrap(); - let mut plan = planner - .plan($query_str.to_string(), None, PlanOptions::default()) - .await - .unwrap(); - - // Field names need sorting - for ty in plan.usage_reporting.referenced_fields_by_type.values_mut() { - ty.field_names.sort(); - } - - insta::with_settings!({sort_maps => true, snapshot_suffix => "bridge"}, { - insta::assert_yaml_snapshot!(plan.usage_reporting); - }); - }; -} - -fn assert_expected_signature(actual: &ComparableUsageReporting, expected_sig: &str) { - assert_eq!(actual.result.stats_report_key, expected_sig); +fn assert_expected_signature(actual: &UsageReporting, expected_sig: &str) { + assert_eq!(actual.stats_report_key, expected_sig); } macro_rules! assert_extended_references { @@ -73,27 +30,12 @@ macro_rules! assert_enums_from_response { }; } -// Generate usage reporting with the same signature and refs doc, and with legacy normalization algorithm -fn generate_legacy( - doc: &ExecutableDocument, - operation_name: &Option, - schema: &Valid, -) -> ComparableUsageReporting { - generate_usage_reporting( - doc, - doc, - operation_name, - schema, - &ApolloSignatureNormalizationAlgorithm::Legacy, - ) -} - // Generate usage reporting with the same signature and refs doc, and with enhanced normalization algorithm fn generate_enhanced( doc: &ExecutableDocument, operation_name: &Option, schema: &Valid, -) -> ComparableUsageReporting { +) -> UsageReporting { generate_usage_reporting( doc, doc, @@ -140,413 +82,6 @@ fn enums_from_response( result } -#[test(tokio::test)] -async fn test_complex_query() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/complex_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("TransformedQuery".into()), &schema); - - assert_generated_report!(generated); - - // the router-bridge planner will throw errors on unused fragments/queries so we remove them here - let sanitised_query_str = include_str!("testdata/complex_query_sanitized.graphql"); - - assert_bridge_results!(schema_str, sanitised_query_str); -} - -#[test(tokio::test)] -async fn test_complex_references() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/complex_references_query.graphql"); - - let schema: Valid = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("Query".into()), &schema); - - assert_generated_report!(generated); - - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_basic_whitespace() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/named_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("MyQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_anonymous_query() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/anonymous_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &None, &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_anonymous_mutation() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/anonymous_mutation.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &None, &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_anonymous_subscription() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str: &str = include_str!("testdata/subscription_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &None, &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_ordered_fields_and_variables() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/ordered_fields_and_variables_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("VariableScalarInputQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_fragments() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/fragments_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("FragmentQuery".into()), &schema); - - assert_generated_report!(generated); - - // the router-bridge planner will throw errors on unused fragments/queries so we remove them here - let sanitised_query_str = r#"query FragmentQuery { - noInputQuery { - listOfBools - interfaceResponse { - sharedField - ... on InterfaceImplementation2 { - implementation2Field - } - ...bbbInterfaceFragment - ...aaaInterfaceFragment - ... { - ... on InterfaceImplementation1 { - implementation1Field - } - } - ... on InterfaceImplementation1 { - implementation1Field - } - } - unionResponse { - ... on UnionType2 { - unionType2Field - } - ... on UnionType1 { - unionType1Field - } - } - ...zzzFragment - ...aaaFragment - ...ZZZFragment - } - } - - fragment zzzFragment on EverythingResponse { - listOfInterfaces { - sharedField - } - } - - fragment ZZZFragment on EverythingResponse { - listOfInterfaces { - sharedField - } - } - - fragment aaaFragment on EverythingResponse { - listOfInterfaces { - sharedField - } - } - - fragment bbbInterfaceFragment on InterfaceImplementation2 { - sharedField - implementation2Field - } - - fragment aaaInterfaceFragment on InterfaceImplementation1 { - sharedField - }"#; - assert_bridge_results!(schema_str, sanitised_query_str); -} - -#[test(tokio::test)] -async fn test_directives() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/directives_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("DirectiveQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_aliases() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/aliases_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("AliasQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_inline_values() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/inline_values_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("InlineInputTypeQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_root_type_fragment() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/root_type_fragment_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &None, &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_directive_arg_spacing() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/directive_arg_spacing_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &None, &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_operation_with_single_variable() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/operation_with_single_variable_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("QueryWithVar".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_operation_with_multiple_variables() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/operation_with_multiple_variables_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("QueryWithVars".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_field_arg_comma_or_space() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/field_arg_comma_or_space_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("QueryArgLength".into()), &schema); - - // enumInputQuery has a variable line length of 81, so it should be separated by spaces (which are converted from newlines - // in the original implementation). - // enumInputQuery has a variable line length of 80, so it should be separated by commas. - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_operation_arg_always_commas() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/operation_arg_always_commas_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("QueryArgLength".into()), &schema); - - // operation variables shouldn't ever be converted to spaces, since the line length check is only on field variables - // in the original implementation - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_comma_separator_always() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/comma_separator_always_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("QueryCommaEdgeCase".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_nested_fragments() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/nested_fragments_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("NestedFragmentQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_mutation_space() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/mutation_space_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("Test_Mutation_Space".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_mutation_comma() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/mutation_comma_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("Test_Mutation_Comma".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_comma_lower_bound() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/comma_lower_bound_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("TestCommaLowerBound".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_comma_upper_bound() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/comma_upper_bound_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("TestCommaUpperBound".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - -#[test(tokio::test)] -async fn test_underscore() { - let schema_str = include_str!("testdata/schema_interop.graphql"); - let query_str = include_str!("testdata/underscore_query.graphql"); - - let schema = Schema::parse_and_validate(schema_str, "schema.graphql").unwrap(); - let doc = ExecutableDocument::parse(&schema, query_str, "query.graphql").unwrap(); - - let generated = generate_legacy(&doc, &Some("UnderscoreQuery".into()), &schema); - - assert_generated_report!(generated); - assert_bridge_results!(schema_str, query_str); -} - #[test(tokio::test)] async fn test_enhanced_uses_comma_always() { let schema_str = include_str!("testdata/schema_interop.graphql"); diff --git a/apollo-router/src/axum_factory/tests.rs b/apollo-router/src/axum_factory/tests.rs index efcf7e3ab3..0aced33790 100644 --- a/apollo-router/src/axum_factory/tests.rs +++ b/apollo-router/src/axum_factory/tests.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::io; use std::net::SocketAddr; -use std::num::NonZeroUsize; use std::pin::Pin; use std::str::FromStr; use std::sync::atomic::AtomicU32; @@ -2267,14 +2266,9 @@ async fn test_supergraph_timeout() { let schema = include_str!("..//testdata/minimal_supergraph.graphql"); let schema = Arc::new(Schema::parse(schema, &conf).unwrap()); - let planner = BridgeQueryPlannerPool::new( - Vec::new(), - schema.clone(), - conf.clone(), - NonZeroUsize::new(1).unwrap(), - ) - .await - .unwrap(); + let planner = BridgeQueryPlannerPool::new(schema.clone(), conf.clone()) + .await + .unwrap(); // we do the entire supergraph rebuilding instead of using `from_supergraph_mock_callback_and_configuration` // because we need the plugins to apply on the supergraph diff --git a/apollo-router/src/configuration/metrics.rs b/apollo-router/src/configuration/metrics.rs index 7241950fd7..4fcfd6c509 100644 --- a/apollo-router/src/configuration/metrics.rs +++ b/apollo-router/src/configuration/metrics.rs @@ -8,7 +8,6 @@ use opentelemetry_api::KeyValue; use paste::paste; use serde_json::Value; -use super::AvailableParallelism; use crate::metrics::meter_provider; use crate::uplink::license_enforcement::LicenseState; use crate::Configuration; @@ -47,9 +46,6 @@ impl Metrics { ); data.populate_license_instrument(license_state); data.populate_user_plugins_instrument(configuration); - data.populate_query_planner_experimental_parallelism(configuration); - data.populate_deno_or_rust_mode_instruments(configuration); - data.populate_legacy_fragment_usage(configuration); data.into() } @@ -497,85 +493,6 @@ impl InstrumentData { ), ); } - - pub(crate) fn populate_legacy_fragment_usage(&mut self, configuration: &Configuration) { - // Fragment generation takes precedence over fragment reuse. Only report when fragment reuse is *actually active*. - if configuration.supergraph.reuse_query_fragments == Some(true) - && !configuration.supergraph.generate_query_fragments - { - self.data.insert( - "apollo.router.config.reuse_query_fragments".to_string(), - (1, HashMap::new()), - ); - } - } - - pub(crate) fn populate_query_planner_experimental_parallelism( - &mut self, - configuration: &Configuration, - ) { - let query_planner_parallelism_config = configuration - .supergraph - .query_planning - .experimental_parallelism; - - if query_planner_parallelism_config != Default::default() { - let mut attributes = HashMap::new(); - attributes.insert( - "mode".to_string(), - if let AvailableParallelism::Auto(_) = query_planner_parallelism_config { - "auto" - } else { - "static" - } - .into(), - ); - self.data.insert( - "apollo.router.config.query_planning.parallelism".to_string(), - ( - configuration - .supergraph - .query_planning - .experimental_query_planner_parallelism() - .map(|n| { - #[cfg(test)] - { - // Set to a fixed number for snapshot tests - if let AvailableParallelism::Auto(_) = - query_planner_parallelism_config - { - return 8; - } - } - let as_usize: usize = n.into(); - let as_u64: u64 = as_usize.try_into().unwrap_or_default(); - as_u64 - }) - .unwrap_or_default(), - attributes, - ), - ); - } - } - - /// Populate metrics on the rollout of experimental Rust replacements of JavaScript code. - pub(crate) fn populate_deno_or_rust_mode_instruments(&mut self, configuration: &Configuration) { - let experimental_query_planner_mode = match configuration.experimental_query_planner_mode { - super::QueryPlannerMode::Legacy => "legacy", - super::QueryPlannerMode::Both => "both", - super::QueryPlannerMode::BothBestEffort => "both_best_effort", - super::QueryPlannerMode::New => "new", - super::QueryPlannerMode::NewBestEffort => "new_best_effort", - }; - - self.data.insert( - "apollo.router.config.experimental_query_planner_mode".to_string(), - ( - 1, - HashMap::from_iter([("mode".to_string(), experimental_query_planner_mode.into())]), - ), - ); - } } impl From for Metrics { @@ -608,9 +525,7 @@ mod test { use crate::configuration::metrics::InstrumentData; use crate::configuration::metrics::Metrics; - use crate::configuration::QueryPlannerMode; use crate::uplink::license_enforcement::LicenseState; - use crate::Configuration; #[derive(RustEmbed)] #[folder = "src/configuration/testdata/metrics"] @@ -628,8 +543,6 @@ mod test { let mut data = InstrumentData::default(); data.populate_config_instruments(yaml); - let configuration: Configuration = input.parse().unwrap(); - data.populate_query_planner_experimental_parallelism(&configuration); let _metrics: Metrics = data.into(); assert_non_zero_metrics_snapshot!(file_name); } @@ -683,35 +596,4 @@ mod test { let _metrics: Metrics = data.into(); assert_non_zero_metrics_snapshot!(); } - - #[test] - fn test_experimental_mode_metrics() { - let mut data = InstrumentData::default(); - data.populate_deno_or_rust_mode_instruments(&Configuration { - experimental_query_planner_mode: QueryPlannerMode::Both, - ..Default::default() - }); - let _metrics: Metrics = data.into(); - assert_non_zero_metrics_snapshot!(); - } - - #[test] - fn test_experimental_mode_metrics_2() { - let mut data = InstrumentData::default(); - // Default query planner value should still be reported - data.populate_deno_or_rust_mode_instruments(&Configuration::default()); - let _metrics: Metrics = data.into(); - assert_non_zero_metrics_snapshot!(); - } - - #[test] - fn test_experimental_mode_metrics_3() { - let mut data = InstrumentData::default(); - data.populate_deno_or_rust_mode_instruments(&Configuration { - experimental_query_planner_mode: QueryPlannerMode::New, - ..Default::default() - }); - let _metrics: Metrics = data.into(); - assert_non_zero_metrics_snapshot!(); - } } diff --git a/apollo-router/src/configuration/migrations/0032-remove-legacy-qp.yaml b/apollo-router/src/configuration/migrations/0032-remove-legacy-qp.yaml new file mode 100644 index 0000000000..281de2b607 --- /dev/null +++ b/apollo-router/src/configuration/migrations/0032-remove-legacy-qp.yaml @@ -0,0 +1,8 @@ +description: the legacy query planner was removed, together with relevant configuration keys experimental_query_planner_mode and supergraph.query_planning.experimental_parallelism +actions: + - type: delete + path: experimental_query_planner_mode + - type: delete + path: supergraph.experimental_reuse_query_fragments + - type: delete + path: supergraph.query_planning.experimental_parallelism diff --git a/apollo-router/src/configuration/mod.rs b/apollo-router/src/configuration/mod.rs index 8bdc97b52a..a201a647cb 100644 --- a/apollo-router/src/configuration/mod.rs +++ b/apollo-router/src/configuration/mod.rs @@ -162,10 +162,6 @@ pub struct Configuration { #[serde(default)] pub(crate) experimental_chaos: Chaos, - /// Set the query planner implementation to use. - #[serde(default)] - pub(crate) experimental_query_planner_mode: QueryPlannerMode, - /// Plugin configuration #[serde(default)] pub(crate) plugins: UserPlugins, @@ -197,40 +193,6 @@ impl PartialEq for Configuration { } } -/// Query planner modes. -#[derive(Clone, PartialEq, Eq, Default, Derivative, Serialize, Deserialize, JsonSchema)] -#[derivative(Debug)] -#[serde(rename_all = "snake_case")] -pub(crate) enum QueryPlannerMode { - /// Use the new Rust-based implementation. - /// - /// Raises an error at Router startup if the the new planner does not support the schema - /// (such as using legacy Apollo Federation 1) - New, - /// Use the old JavaScript-based implementation. - Legacy, - /// Use primarily the Javascript-based implementation, - /// but also schedule background jobs to run the Rust implementation and compare results, - /// logging warnings if the implementations disagree. - /// - /// Raises an error at Router startup if the the new planner does not support the schema - /// (such as using legacy Apollo Federation 1) - Both, - /// Use primarily the Javascript-based implementation, - /// but also schedule on a best-effort basis background jobs - /// to run the Rust implementation and compare results, - /// logging warnings if the implementations disagree. - /// - /// Falls back to `legacy` with a warning - /// if the the new planner does not support the schema - /// (such as using legacy Apollo Federation 1) - BothBestEffort, - /// Use the new Rust-based implementation but fall back to the legacy one - /// for supergraph schemas composed with legacy Apollo Federation 1. - #[default] - NewBestEffort, -} - impl<'de> serde::Deserialize<'de> for Configuration { fn deserialize(deserializer: D) -> Result where @@ -256,7 +218,6 @@ impl<'de> serde::Deserialize<'de> for Configuration { experimental_chaos: Chaos, batching: Batching, experimental_type_conditioned_fetching: bool, - experimental_query_planner_mode: QueryPlannerMode, } let mut ad_hoc: AdHocConfiguration = serde::Deserialize::deserialize(deserializer)?; @@ -283,7 +244,6 @@ impl<'de> serde::Deserialize<'de> for Configuration { limits: ad_hoc.limits, experimental_chaos: ad_hoc.experimental_chaos, experimental_type_conditioned_fetching: ad_hoc.experimental_type_conditioned_fetching, - experimental_query_planner_mode: ad_hoc.experimental_query_planner_mode, plugins: ad_hoc.plugins, apollo_plugins: ad_hoc.apollo_plugins, batching: ad_hoc.batching, @@ -329,7 +289,6 @@ impl Configuration { uplink: Option, experimental_type_conditioned_fetching: Option, batching: Option, - experimental_query_planner_mode: Option, ) -> Result { let notify = Self::notify(&apollo_plugins)?; @@ -344,7 +303,6 @@ impl Configuration { persisted_queries: persisted_query.unwrap_or_default(), limits: operation_limits.unwrap_or_default(), experimental_chaos: chaos.unwrap_or_default(), - experimental_query_planner_mode: experimental_query_planner_mode.unwrap_or_default(), plugins: UserPlugins { plugins: Some(plugins), }, @@ -396,27 +354,6 @@ impl Configuration { ).build()) } - pub(crate) fn js_query_planner_config(&self) -> router_bridge::planner::QueryPlannerConfig { - router_bridge::planner::QueryPlannerConfig { - reuse_query_fragments: self.supergraph.reuse_query_fragments, - generate_query_fragments: Some(self.supergraph.generate_query_fragments), - incremental_delivery: Some(router_bridge::planner::IncrementalDeliverySupport { - enable_defer: Some(self.supergraph.defer_support), - }), - graphql_validation: false, - debug: Some(router_bridge::planner::QueryPlannerDebugConfig { - bypass_planner_for_single_subgraph: None, - max_evaluated_plans: self - .supergraph - .query_planning - .experimental_plans_limit - .or(Some(10000)), - paths_limit: self.supergraph.query_planning.experimental_paths_limit, - }), - type_conditioned_fetching: self.experimental_type_conditioned_fetching, - } - } - pub(crate) fn rust_query_planner_config( &self, ) -> apollo_federation::query_plan::query_planner::QueryPlannerConfig { @@ -475,7 +412,6 @@ impl Configuration { uplink: Option, batching: Option, experimental_type_conditioned_fetching: Option, - experimental_query_planner_mode: Option, ) -> Result { let configuration = Self { validated_yaml: Default::default(), @@ -486,7 +422,6 @@ impl Configuration { cors: cors.unwrap_or_default(), limits: operation_limits.unwrap_or_default(), experimental_chaos: chaos.unwrap_or_default(), - experimental_query_planner_mode: experimental_query_planner_mode.unwrap_or_default(), plugins: UserPlugins { plugins: Some(plugins), }, @@ -700,12 +635,6 @@ pub(crate) struct Supergraph { /// Default: false pub(crate) introspection: bool, - /// Enable reuse of query fragments - /// This feature is deprecated and will be removed in next release. - /// The config can only be set when the legacy query planner is explicitly enabled. - #[serde(rename = "experimental_reuse_query_fragments")] - pub(crate) reuse_query_fragments: Option, - /// Enable QP generation of fragments for subgraph requests /// Default: true pub(crate) generate_query_fragments: bool, @@ -731,25 +660,12 @@ const fn default_generate_query_fragments() -> bool { true } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case", untagged)] -pub(crate) enum AvailableParallelism { - Auto(Auto), - Fixed(NonZeroUsize), -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub(crate) enum Auto { Auto, } -impl Default for AvailableParallelism { - fn default() -> Self { - Self::Fixed(NonZeroUsize::new(1).expect("cannot fail")) - } -} - fn default_defer_support() -> bool { true } @@ -763,7 +679,6 @@ impl Supergraph { introspection: Option, defer_support: Option, query_planning: Option, - reuse_query_fragments: Option, generate_query_fragments: Option, early_cancel: Option, experimental_log_on_broken_pipe: Option, @@ -774,15 +689,6 @@ impl Supergraph { introspection: introspection.unwrap_or_else(default_graphql_introspection), defer_support: defer_support.unwrap_or_else(default_defer_support), query_planning: query_planning.unwrap_or_default(), - reuse_query_fragments: generate_query_fragments.and_then(|v| - if v { - if reuse_query_fragments.is_some_and(|v| v) { - // warn the user that both are enabled and it's overridden - tracing::warn!("Both 'generate_query_fragments' and 'experimental_reuse_query_fragments' are explicitly enabled, 'experimental_reuse_query_fragments' will be overridden to false"); - } - Some(false) - } else { reuse_query_fragments } - ), generate_query_fragments: generate_query_fragments .unwrap_or_else(default_generate_query_fragments), early_cancel: early_cancel.unwrap_or_default(), @@ -801,7 +707,6 @@ impl Supergraph { introspection: Option, defer_support: Option, query_planning: Option, - reuse_query_fragments: Option, generate_query_fragments: Option, early_cancel: Option, experimental_log_on_broken_pipe: Option, @@ -812,15 +717,6 @@ impl Supergraph { introspection: introspection.unwrap_or_else(default_graphql_introspection), defer_support: defer_support.unwrap_or_else(default_defer_support), query_planning: query_planning.unwrap_or_default(), - reuse_query_fragments: generate_query_fragments.and_then(|v| - if v { - if reuse_query_fragments.is_some_and(|v| v) { - // warn the user that both are enabled and it's overridden - tracing::warn!("Both 'generate_query_fragments' and 'experimental_reuse_query_fragments' are explicitly enabled, 'experimental_reuse_query_fragments' will be overridden to false"); - } - Some(false) - } else { reuse_query_fragments } - ), generate_query_fragments: generate_query_fragments .unwrap_or_else(default_generate_query_fragments), early_cancel: early_cancel.unwrap_or_default(), @@ -944,19 +840,6 @@ pub(crate) struct QueryPlanning { /// If cache warm up is configured, this will allow the router to keep a query plan created with /// the old schema, if it determines that the schema update does not affect the corresponding query pub(crate) experimental_reuse_query_plans: bool, - - /// Set the size of a pool of workers to enable query planning parallelism. - /// Default: 1. - pub(crate) experimental_parallelism: AvailableParallelism, -} - -impl QueryPlanning { - pub(crate) fn experimental_query_planner_parallelism(&self) -> io::Result { - match self.experimental_parallelism { - AvailableParallelism::Auto(Auto::Auto) => std::thread::available_parallelism(), - AvailableParallelism::Fixed(n) => Ok(n), - } - } } /// Cache configuration diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics.snap deleted file mode 100644 index 513298fd51..0000000000 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: apollo-router/src/configuration/metrics.rs -expression: "&metrics.non_zero()" ---- -- name: apollo.router.config.experimental_query_planner_mode - data: - datapoints: - - value: 1 - attributes: - mode: both diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics_2.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics_2.snap deleted file mode 100644 index e976e8391d..0000000000 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics_2.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: apollo-router/src/configuration/metrics.rs -expression: "&metrics.non_zero()" ---- -- name: apollo.router.config.experimental_query_planner_mode - data: - datapoints: - - value: 1 - attributes: - mode: new_best_effort diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics_3.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics_3.snap deleted file mode 100644 index 34d0b7fe07..0000000000 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__metrics__test__experimental_mode_metrics_3.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: apollo-router/src/configuration/metrics.rs -expression: "&metrics.non_zero()" ---- -- name: apollo.router.config.experimental_query_planner_mode - data: - datapoints: - - value: 1 - attributes: - mode: new diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 4ae8457f17..2713e63dc5 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -307,25 +307,6 @@ expression: "&schema" } ] }, - "Auto": { - "enum": [ - "auto" - ], - "type": "string" - }, - "AvailableParallelism": { - "anyOf": [ - { - "$ref": "#/definitions/Auto", - "description": "#/definitions/Auto" - }, - { - "format": "uint", - "minimum": 1.0, - "type": "integer" - } - ] - }, "BatchProcessorConfig": { "description": "Batch processor configuration", "properties": { @@ -4453,46 +4434,6 @@ expression: "&schema" ], "type": "object" }, - "QueryPlannerMode": { - "description": "Query planner modes.", - "oneOf": [ - { - "description": "Use the new Rust-based implementation.\n\nRaises an error at Router startup if the the new planner does not support the schema (such as using legacy Apollo Federation 1)", - "enum": [ - "new" - ], - "type": "string" - }, - { - "description": "Use the old JavaScript-based implementation.", - "enum": [ - "legacy" - ], - "type": "string" - }, - { - "description": "Use primarily the Javascript-based implementation, but also schedule background jobs to run the Rust implementation and compare results, logging warnings if the implementations disagree.\n\nRaises an error at Router startup if the the new planner does not support the schema (such as using legacy Apollo Federation 1)", - "enum": [ - "both" - ], - "type": "string" - }, - { - "description": "Use primarily the Javascript-based implementation, but also schedule on a best-effort basis background jobs to run the Rust implementation and compare results, logging warnings if the implementations disagree.\n\nFalls back to `legacy` with a warning if the the new planner does not support the schema (such as using legacy Apollo Federation 1)", - "enum": [ - "both_best_effort" - ], - "type": "string" - }, - { - "description": "Use the new Rust-based implementation but fall back to the legacy one for supergraph schemas composed with legacy Apollo Federation 1.", - "enum": [ - "new_best_effort" - ], - "type": "string" - } - ] - }, "QueryPlanning": { "additionalProperties": false, "description": "Query planning cache configuration", @@ -4501,10 +4442,6 @@ expression: "&schema" "$ref": "#/definitions/QueryPlanCache", "description": "#/definitions/QueryPlanCache" }, - "experimental_parallelism": { - "$ref": "#/definitions/AvailableParallelism", - "description": "#/definitions/AvailableParallelism" - }, "experimental_paths_limit": { "default": null, "description": "Before creating query plans, for each path of fields in the query we compute all the possible options to traverse that path via the subgraphs. Multiple options can arise because fields in the path can be provided by multiple subgraphs, and abstract types (i.e. unions and interfaces) returned by fields sometimes require the query planner to traverse through each constituent object type. The number of options generated in this computation can grow large if the schema or query are sufficiently complex, and that will increase the time spent planning.\n\nThis config allows specifying a per-path limit to the number of options considered. If any path's options exceeds this limit, query planning will abort and the operation will fail.\n\nThe default value is None, which specifies no limit.", @@ -6649,12 +6586,6 @@ expression: "&schema" "description": "Log a message if the client closes the connection before the response is sent. Default: false.", "type": "boolean" }, - "experimental_reuse_query_fragments": { - "default": null, - "description": "Enable reuse of query fragments This feature is deprecated and will be removed in next release. The config can only be set when the legacy query planner is explicitly enabled.", - "nullable": true, - "type": "boolean" - }, "generate_query_fragments": { "default": true, "description": "Enable QP generation of fragments for subgraph requests Default: true", @@ -8376,10 +8307,6 @@ expression: "&schema" "$ref": "#/definitions/Chaos", "description": "#/definitions/Chaos" }, - "experimental_query_planner_mode": { - "$ref": "#/definitions/QueryPlannerMode", - "description": "#/definitions/QueryPlannerMode" - }, "experimental_type_conditioned_fetching": { "default": false, "description": "Type conditioned fetching configuration.", diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__upgrade_old_configuration@legacy_qp.yaml.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__upgrade_old_configuration@legacy_qp.yaml.snap new file mode 100644 index 0000000000..44ea1678e6 --- /dev/null +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__upgrade_old_configuration@legacy_qp.yaml.snap @@ -0,0 +1,7 @@ +--- +source: apollo-router/src/configuration/tests.rs +expression: new_config +--- +--- +supergraph: + query_planning: {} diff --git a/apollo-router/src/configuration/testdata/metrics/query_planner_parallelism_auto.router.yaml b/apollo-router/src/configuration/testdata/metrics/query_planner_parallelism_auto.router.yaml deleted file mode 100644 index e29357f06d..0000000000 --- a/apollo-router/src/configuration/testdata/metrics/query_planner_parallelism_auto.router.yaml +++ /dev/null @@ -1,3 +0,0 @@ -supergraph: - query_planning: - experimental_parallelism: auto diff --git a/apollo-router/src/configuration/testdata/metrics/query_planner_parallelism_static.router.yaml b/apollo-router/src/configuration/testdata/metrics/query_planner_parallelism_static.router.yaml deleted file mode 100644 index 8861ab2777..0000000000 --- a/apollo-router/src/configuration/testdata/metrics/query_planner_parallelism_static.router.yaml +++ /dev/null @@ -1,3 +0,0 @@ -supergraph: - query_planning: - experimental_parallelism: 10 diff --git a/apollo-router/src/configuration/testdata/migrations/legacy_qp.yaml b/apollo-router/src/configuration/testdata/migrations/legacy_qp.yaml new file mode 100644 index 0000000000..4408d941ec --- /dev/null +++ b/apollo-router/src/configuration/testdata/migrations/legacy_qp.yaml @@ -0,0 +1,5 @@ +supergraph: + query_planning: + experimental_parallelism: 2 + experimental_reuse_query_fragments: true +experimental_query_planner_mode: legacy diff --git a/apollo-router/src/error.rs b/apollo-router/src/error.rs index 850634be2b..04efe566e9 100644 --- a/apollo-router/src/error.rs +++ b/apollo-router/src/error.rs @@ -7,15 +7,13 @@ use apollo_compiler::validation::WithErrors; use apollo_federation::error::FederationError; use displaydoc::Display; use lazy_static::__Deref; -use router_bridge::introspect::IntrospectionError; -use router_bridge::planner::PlannerError; -use router_bridge::planner::UsageReporting; use serde::Deserialize; use serde::Serialize; use thiserror::Error; use tokio::task::JoinError; use tower::BoxError; +use crate::apollo_studio_interop::UsageReporting; pub(crate) use crate::configuration::ConfigurationError; pub(crate) use crate::graphql::Error; use crate::graphql::ErrorExtension; @@ -236,9 +234,6 @@ impl From for CacheResolverError { /// Error types for service building. #[derive(Error, Debug, Display)] pub(crate) enum ServiceBuildError { - /// couldn't build Query Planner Service: {0} - QueryPlannerError(QueryPlannerError), - /// failed to initialize the query planner: {0} QpInitError(FederationError), @@ -255,18 +250,6 @@ impl From for ServiceBuildError { } } -impl From> for ServiceBuildError { - fn from(errors: Vec) -> Self { - ServiceBuildError::QueryPlannerError(errors.into()) - } -} - -impl From for ServiceBuildError { - fn from(error: router_bridge::error::Error) -> Self { - ServiceBuildError::QueryPlannerError(error.into()) - } -} - impl From for ServiceBuildError { fn from(err: BoxError) -> Self { ServiceBuildError::ServiceError(err) @@ -276,15 +259,9 @@ impl From for ServiceBuildError { /// Error types for QueryPlanner #[derive(Error, Debug, Display, Clone, Serialize, Deserialize)] pub(crate) enum QueryPlannerError { - /// couldn't instantiate query planner; invalid schema: {0} - SchemaValidationErrors(PlannerErrors), - /// invalid query: {0} OperationValidationErrors(ValidationErrors), - /// couldn't plan query: {0} - PlanningErrors(PlanErrors), - /// query planning panicked: {0} JoinError(String), @@ -297,15 +274,9 @@ pub(crate) enum QueryPlannerError { /// unhandled planner result UnhandledPlannerResult, - /// router bridge error: {0} - RouterBridgeError(router_bridge::error::Error), - /// spec error: {0} SpecError(SpecError), - /// introspection error: {0} - Introspection(IntrospectionError), - /// complexity limit exceeded LimitExceeded(OperationLimits), @@ -402,25 +373,11 @@ impl IntoGraphQLErrors for QueryPlannerError { QueryPlannerError::SpecError(err) => err .into_graphql_errors() .map_err(QueryPlannerError::SpecError), - QueryPlannerError::SchemaValidationErrors(errs) => errs - .into_graphql_errors() - .map_err(QueryPlannerError::SchemaValidationErrors), + QueryPlannerError::OperationValidationErrors(errs) => errs .into_graphql_errors() .map_err(QueryPlannerError::OperationValidationErrors), - QueryPlannerError::PlanningErrors(planning_errors) => Ok(planning_errors - .errors - .iter() - .map(|p_err| Error::from(p_err.clone())) - .collect()), - QueryPlannerError::Introspection(introspection_error) => Ok(vec![Error::builder() - .message( - introspection_error - .message - .unwrap_or_else(|| "introspection error".to_string()), - ) - .extension_code("INTROSPECTION_ERROR") - .build()]), + QueryPlannerError::LimitExceeded(OperationLimits { depth, height, @@ -471,7 +428,6 @@ impl IntoGraphQLErrors for QueryPlannerError { impl QueryPlannerError { pub(crate) fn usage_reporting(&self) -> Option { match self { - QueryPlannerError::PlanningErrors(pe) => Some(pe.usage_reporting.clone()), QueryPlannerError::SpecError(e) => Some(UsageReporting { stats_report_key: e.get_error_key().to_string(), referenced_fields_by_type: HashMap::new(), @@ -481,49 +437,6 @@ impl QueryPlannerError { } } -#[derive(Clone, Debug, Error, Serialize, Deserialize)] -/// Container for planner setup errors -pub(crate) struct PlannerErrors(Arc>); - -impl IntoGraphQLErrors for PlannerErrors { - fn into_graphql_errors(self) -> Result, Self> { - let errors = self.0.iter().map(|e| Error::from(e.clone())).collect(); - - Ok(errors) - } -} - -impl std::fmt::Display for PlannerErrors { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "schema validation errors: {}", - self.0 - .iter() - .map(std::string::ToString::to_string) - .collect::>() - .join(", ") - )) - } -} - -impl From> for QueryPlannerError { - fn from(errors: Vec) -> Self { - QueryPlannerError::SchemaValidationErrors(PlannerErrors(Arc::new(errors))) - } -} - -impl From for QueryPlannerError { - fn from(errors: router_bridge::planner::PlanErrors) -> Self { - QueryPlannerError::PlanningErrors(errors.into()) - } -} - -impl From for QueryPlannerError { - fn from(errors: PlanErrors) -> Self { - QueryPlannerError::PlanningErrors(errors) - } -} - impl From for QueryPlannerError { fn from(err: JoinError) -> Self { QueryPlannerError::JoinError(err.to_string()) @@ -552,12 +465,6 @@ impl From for QueryPlannerError { QueryPlannerError::OperationValidationErrors(ValidationErrors { errors: err.errors }) } } - -impl From for QueryPlannerError { - fn from(error: router_bridge::error::Error) -> Self { - QueryPlannerError::RouterBridgeError(error) - } -} impl From> for QueryPlannerError { fn from(error: OperationLimits) -> Self { QueryPlannerError::LimitExceeded(error) @@ -570,51 +477,6 @@ impl From for Response { } } -/// The payload if the plan_worker invocation failed -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct PlanErrors { - /// The errors the plan_worker invocation failed with - pub(crate) errors: Arc>, - /// Usage reporting related data such as the - /// operation signature and referenced fields - pub(crate) usage_reporting: UsageReporting, -} - -impl From for PlanErrors { - fn from( - router_bridge::planner::PlanErrors { - errors, - usage_reporting, - }: router_bridge::planner::PlanErrors, - ) -> Self { - PlanErrors { - errors, - usage_reporting, - } - } -} - -impl std::fmt::Display for PlanErrors { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "query validation errors: {}", - format_bridge_errors(&self.errors) - )) - } -} - -pub(crate) fn format_bridge_errors(errors: &[router_bridge::planner::PlanError]) -> String { - errors - .iter() - .map(|e| { - e.message - .clone() - .unwrap_or_else(|| "UNKNWON ERROR".to_string()) - }) - .collect::>() - .join(", ") -} - /// Error in the schema. #[derive(Debug, Error, Display, derive_more::From)] #[non_exhaustive] @@ -797,38 +659,4 @@ mod tests { assert_eq!(expected_gql_error, error.to_graphql_error(None)); } - - #[test] - fn test_into_graphql_error_introspection_with_message_handled_correctly() { - let expected_message = "no can introspect".to_string(); - let ie = IntrospectionError { - message: Some(expected_message.clone()), - }; - let error = QueryPlannerError::Introspection(ie); - let mut graphql_errors = error.into_graphql_errors().expect("vec of graphql errors"); - assert_eq!(graphql_errors.len(), 1); - let first_error = graphql_errors.pop().expect("has to be one error"); - assert_eq!(first_error.message, expected_message); - assert_eq!(first_error.extensions.len(), 1); - assert_eq!( - first_error.extensions.get("code").expect("has code"), - "INTROSPECTION_ERROR" - ); - } - - #[test] - fn test_into_graphql_error_introspection_without_message_handled_correctly() { - let expected_message = "introspection error".to_string(); - let ie = IntrospectionError { message: None }; - let error = QueryPlannerError::Introspection(ie); - let mut graphql_errors = error.into_graphql_errors().expect("vec of graphql errors"); - assert_eq!(graphql_errors.len(), 1); - let first_error = graphql_errors.pop().expect("has to be one error"); - assert_eq!(first_error.message, expected_message); - assert_eq!(first_error.extensions.len(), 1); - assert_eq!( - first_error.extensions.get("code").expect("has code"), - "INTROSPECTION_ERROR" - ); - } } diff --git a/apollo-router/src/graphql/mod.rs b/apollo-router/src/graphql/mod.rs index 7ec24d7dd7..0ac60d3f69 100644 --- a/apollo-router/src/graphql/mod.rs +++ b/apollo-router/src/graphql/mod.rs @@ -14,15 +14,8 @@ use heck::ToShoutySnakeCase; pub use request::Request; pub use response::IncrementalResponse; pub use response::Response; -pub use router_bridge::planner::Location; -use router_bridge::planner::PlanError; -use router_bridge::planner::PlanErrorExtensions; -use router_bridge::planner::PlannerError; -use router_bridge::planner::WorkerError; -use router_bridge::planner::WorkerGraphQLError; use serde::Deserialize; use serde::Serialize; -use serde_json_bytes::json; use serde_json_bytes::ByteString; use serde_json_bytes::Map as JsonMap; use serde_json_bytes::Value; @@ -44,6 +37,16 @@ pub use crate::json_ext::PathElement as JsonPathElement; /// even if that stream happens to only contain one item. pub type ResponseStream = Pin + Send>>; +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +/// The error location +pub struct Location { + /// The line number + pub line: u32, + /// The column number + pub column: u32, +} + /// A [GraphQL error](https://spec.graphql.org/October2021/#sec-Errors) /// as may be found in the `errors` field of a GraphQL [`Response`]. /// @@ -211,73 +214,6 @@ where } } -impl ErrorExtension for PlanError {} - -impl From for Error { - fn from(err: PlanError) -> Self { - let extension_code = err.extension_code(); - let extensions = err - .extensions - .map(convert_extensions_to_map) - .unwrap_or_else(move || { - let mut object = Object::new(); - object.insert("code", extension_code.into()); - object - }); - Self { - message: err.message.unwrap_or_else(|| String::from("plan error")), - extensions, - ..Default::default() - } - } -} - -impl ErrorExtension for PlannerError { - fn extension_code(&self) -> String { - match self { - PlannerError::WorkerGraphQLError(worker_graphql_error) => worker_graphql_error - .extensions - .as_ref() - .map(|ext| ext.code.clone()) - .unwrap_or_else(|| worker_graphql_error.extension_code()), - PlannerError::WorkerError(worker_error) => worker_error - .extensions - .as_ref() - .map(|ext| ext.code.clone()) - .unwrap_or_else(|| worker_error.extension_code()), - } - } -} - -impl From for Error { - fn from(err: PlannerError) -> Self { - match err { - PlannerError::WorkerGraphQLError(err) => err.into(), - PlannerError::WorkerError(err) => err.into(), - } - } -} - -impl ErrorExtension for WorkerError {} - -impl From for Error { - fn from(err: WorkerError) -> Self { - let extension_code = err.extension_code(); - let mut extensions = err - .extensions - .map(convert_extensions_to_map) - .unwrap_or_default(); - extensions.insert("code", extension_code.into()); - - Self { - message: err.message.unwrap_or_else(|| String::from("worker error")), - locations: err.locations.into_iter().map(Location::from).collect(), - extensions, - ..Default::default() - } - } -} - impl From for Error { fn from(error: CompilerExecutionError) -> Self { let CompilerExecutionError { @@ -315,37 +251,3 @@ impl From for Error { } } } - -impl ErrorExtension for WorkerGraphQLError {} - -impl From for Error { - fn from(err: WorkerGraphQLError) -> Self { - let extension_code = err.extension_code(); - let mut extensions = err - .extensions - .map(convert_extensions_to_map) - .unwrap_or_default(); - extensions.insert("code", extension_code.into()); - Self { - message: err.message, - locations: err.locations.into_iter().map(Location::from).collect(), - extensions, - ..Default::default() - } - } -} - -fn convert_extensions_to_map(ext: PlanErrorExtensions) -> Object { - let mut extensions = Object::new(); - extensions.insert("code", ext.code.into()); - if let Some(exception) = ext.exception { - extensions.insert( - "exception", - json!({ - "stacktrace": serde_json_bytes::Value::from(exception.stacktrace) - }), - ); - } - - extensions -} diff --git a/apollo-router/src/graphql/response.rs b/apollo-router/src/graphql/response.rs index 6b44f1d2cc..1f710e7add 100644 --- a/apollo-router/src/graphql/response.rs +++ b/apollo-router/src/graphql/response.rs @@ -276,11 +276,11 @@ impl From for Response { #[cfg(test)] mod tests { - use router_bridge::planner::Location; use serde_json::json; use serde_json_bytes::json as bjson; use super::*; + use crate::graphql::Location; #[test] fn test_append_errors_path_fallback_and_override() { diff --git a/apollo-router/src/lib.rs b/apollo-router/src/lib.rs index cc39b79aea..2a62b7c25b 100644 --- a/apollo-router/src/lib.rs +++ b/apollo-router/src/lib.rs @@ -110,16 +110,10 @@ pub mod _private { // Reexports for macros pub use linkme; pub use once_cell; - pub use router_bridge; pub use serde_json; pub use crate::plugin::PluginFactory; pub use crate::plugin::PLUGINS; - // For comparison/fuzzing - pub use crate::query_planner::bridge_query_planner::QueryPlanResult; - pub use crate::query_planner::plan_compare::diff_plan; - pub use crate::query_planner::plan_compare::plan_matches; - pub use crate::query_planner::plan_compare::render_diff; // For tests pub use crate::router_factory::create_test_service_factory_from_yaml; } diff --git a/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs b/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs index ca7217ba7f..d212501660 100644 --- a/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs +++ b/apollo-router/src/plugins/demand_control/cost_calculator/static_cost.rs @@ -629,7 +629,6 @@ mod tests { use ahash::HashMapExt; use apollo_federation::query_plan::query_planner::QueryPlanner; use bytes::Bytes; - use router_bridge::planner::PlanOptions; use test_log::test; use tower::Service; @@ -638,6 +637,7 @@ mod tests { use crate::plugins::authorization::CacheKeyMetadata; use crate::query_planner::BridgeQueryPlanner; use crate::services::layers::query_analysis::ParsedDocument; + use crate::services::query_planner::PlanOptions; use crate::services::QueryPlannerContent; use crate::services::QueryPlannerRequest; use crate::spec; @@ -725,8 +725,6 @@ mod tests { let mut planner = BridgeQueryPlanner::new( schema.into(), config.clone(), - None, - None, Arc::new(IntrospectionCache::new(&config)), ) .await diff --git a/apollo-router/src/plugins/include_subgraph_errors.rs b/apollo-router/src/plugins/include_subgraph_errors.rs index 5641d0a506..5bc29a3237 100644 --- a/apollo-router/src/plugins/include_subgraph_errors.rs +++ b/apollo-router/src/plugins/include_subgraph_errors.rs @@ -88,7 +88,6 @@ impl Plugin for IncludeSubgraphErrors { #[cfg(test)] mod test { - use std::num::NonZeroUsize; use std::sync::Arc; use bytes::Bytes; @@ -211,14 +210,9 @@ mod test { include_str!("../../../apollo-router-benchmarks/benches/fixtures/supergraph.graphql"); let schema = Schema::parse(schema, &configuration).unwrap(); - let planner = BridgeQueryPlannerPool::new( - Vec::new(), - schema.into(), - Arc::clone(&configuration), - NonZeroUsize::new(1).unwrap(), - ) - .await - .unwrap(); + let planner = BridgeQueryPlannerPool::new(schema.into(), Arc::clone(&configuration)) + .await + .unwrap(); let schema = planner.schema(); let subgraph_schemas = planner.subgraph_schemas(); diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index 49fcbdf935..21f8993a93 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -47,7 +47,6 @@ use opentelemetry_semantic_conventions::trace::HTTP_REQUEST_METHOD; use parking_lot::Mutex; use parking_lot::RwLock; use rand::Rng; -use router_bridge::planner::UsageReporting; use serde_json_bytes::json; use serde_json_bytes::ByteString; use serde_json_bytes::Map; @@ -84,6 +83,7 @@ use self::tracing::apollo_telemetry::CLIENT_NAME_KEY; use self::tracing::apollo_telemetry::CLIENT_VERSION_KEY; use crate::apollo_studio_interop::ExtendedReferenceStats; use crate::apollo_studio_interop::ReferencedEnums; +use crate::apollo_studio_interop::UsageReporting; use crate::context::CONTAINS_GRAPHQL_ERROR; use crate::context::OPERATION_KIND; use crate::context::OPERATION_NAME; @@ -1899,7 +1899,7 @@ fn licensed_operation_count(stats_report_key: &str) -> u64 { } fn convert( - referenced_fields: router_bridge::planner::ReferencedFieldsForType, + referenced_fields: crate::apollo_studio_interop::ReferencedFieldsForType, ) -> crate::plugins::telemetry::apollo_exporter::proto::reports::ReferencedFieldsForType { crate::plugins::telemetry::apollo_exporter::proto::reports::ReferencedFieldsForType { field_names: referenced_fields.field_names, diff --git a/apollo-router/src/plugins/test.rs b/apollo-router/src/plugins/test.rs index 8535c9f5f1..0da442c3f2 100644 --- a/apollo-router/src/plugins/test.rs +++ b/apollo-router/src/plugins/test.rs @@ -15,7 +15,6 @@ use crate::plugin::DynPlugin; use crate::plugin::PluginInit; use crate::plugin::PluginPrivate; use crate::query_planner::BridgeQueryPlanner; -use crate::query_planner::PlannerMode; use crate::services::execution; use crate::services::http; use crate::services::router; @@ -95,17 +94,10 @@ impl> + 'static> PluginTestHarness { let schema = Schema::parse(schema, &config).unwrap(); let sdl = schema.raw_sdl.clone(); let supergraph = schema.supergraph_schema().clone(); - let rust_planner = PlannerMode::maybe_rust(&schema, &config).unwrap(); let introspection = Arc::new(IntrospectionCache::new(&config)); - let planner = BridgeQueryPlanner::new( - schema.into(), - Arc::new(config), - None, - rust_planner, - introspection, - ) - .await - .unwrap(); + let planner = BridgeQueryPlanner::new(schema.into(), Arc::new(config), introspection) + .await + .unwrap(); (sdl, supergraph, planner.subgraph_schemas()) } else { ( diff --git a/apollo-router/src/plugins/traffic_shaping/mod.rs b/apollo-router/src/plugins/traffic_shaping/mod.rs index 935a670164..3c803685cf 100644 --- a/apollo-router/src/plugins/traffic_shaping/mod.rs +++ b/apollo-router/src/plugins/traffic_shaping/mod.rs @@ -471,7 +471,6 @@ register_plugin!("apollo", "traffic_shaping", TrafficShaping); #[cfg(test)] mod test { - use std::num::NonZeroUsize; use std::sync::Arc; use bytes::Bytes; @@ -586,14 +585,9 @@ mod test { let config = Arc::new(config); let schema = Arc::new(Schema::parse(schema, &config).unwrap()); - let planner = BridgeQueryPlannerPool::new( - Vec::new(), - schema.clone(), - config.clone(), - NonZeroUsize::new(1).unwrap(), - ) - .await - .unwrap(); + let planner = BridgeQueryPlannerPool::new(schema.clone(), config.clone()) + .await + .unwrap(); let subgraph_schemas = planner.subgraph_schemas(); let mut builder = diff --git a/apollo-router/src/query_planner/bridge_query_planner.rs b/apollo-router/src/query_planner/bridge_query_planner.rs index 10edd392ae..e14c174887 100644 --- a/apollo-router/src/query_planner/bridge_query_planner.rs +++ b/apollo-router/src/query_planner/bridge_query_planner.rs @@ -17,10 +17,6 @@ use futures::future::BoxFuture; use opentelemetry_api::metrics::MeterProvider as _; use opentelemetry_api::metrics::ObservableGauge; use opentelemetry_api::KeyValue; -use router_bridge::planner::PlanOptions; -use router_bridge::planner::PlanSuccess; -use router_bridge::planner::Planner; -use router_bridge::planner::UsageReporting; use serde::Deserialize; use serde_json_bytes::Value; use tower::Service; @@ -29,11 +25,8 @@ use super::PlanNode; use super::QueryKey; use crate::apollo_studio_interop::generate_usage_reporting; use crate::compute_job; -use crate::configuration::QueryPlannerMode; use crate::error::FederationErrorBridge; -use crate::error::PlanErrors; use crate::error::QueryPlannerError; -use crate::error::SchemaError; use crate::error::ServiceBuildError; use crate::error::ValidationErrors; use crate::graphql; @@ -47,11 +40,11 @@ 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::dual_query_planner::BothModeComparisonJob; use crate::query_planner::fetch::QueryHash; use crate::query_planner::labeler::add_defer_labels; use crate::services::layers::query_analysis::ParsedDocument; use crate::services::layers::query_analysis::ParsedDocumentInner; +use crate::services::query_planner::PlanOptions; use crate::services::QueryPlannerContent; use crate::services::QueryPlannerRequest; use crate::services::QueryPlannerResponse; @@ -63,7 +56,6 @@ use crate::spec::SpecError; use crate::Configuration; pub(crate) const RUST_QP_MODE: &str = "rust"; -pub(crate) const JS_QP_MODE: &str = "js"; const UNSUPPORTED_FED1: &str = "fed1"; const INTERNAL_INIT_ERROR: &str = "internal"; @@ -82,13 +74,9 @@ pub(crate) struct BridgeQueryPlanner { introspection: Arc, } +// TODO: inline into parent struct #[derive(Clone)] pub(crate) enum PlannerMode { - Js(Arc>), - Both { - js: Arc>, - rust: Arc, - }, Rust(Arc), } @@ -109,67 +97,6 @@ fn federation_version_instrument(federation_version: Option) -> ObservableG } impl PlannerMode { - async fn new( - schema: &Schema, - configuration: &Configuration, - old_planner: &Option>>, - rust_planner: Option>, - ) -> Result { - Ok(match configuration.experimental_query_planner_mode { - QueryPlannerMode::New => Self::Rust( - rust_planner - .expect("expected Rust QP instance for `experimental_query_planner_mode: new`"), - ), - QueryPlannerMode::Legacy => { - Self::Js(Self::js_planner(&schema.raw_sdl, configuration, old_planner).await?) - } - QueryPlannerMode::Both => Self::Both { - js: Self::js_planner(&schema.raw_sdl, configuration, old_planner).await?, - rust: rust_planner.expect( - "expected Rust QP instance for `experimental_query_planner_mode: both`", - ), - }, - QueryPlannerMode::BothBestEffort => { - if let Some(rust) = rust_planner { - Self::Both { - js: Self::js_planner(&schema.raw_sdl, configuration, old_planner).await?, - rust, - } - } else { - Self::Js(Self::js_planner(&schema.raw_sdl, configuration, old_planner).await?) - } - } - QueryPlannerMode::NewBestEffort => { - if let Some(rust) = rust_planner { - Self::Rust(rust) - } else { - Self::Js(Self::js_planner(&schema.raw_sdl, configuration, old_planner).await?) - } - } - }) - } - - pub(crate) fn maybe_rust( - schema: &Schema, - configuration: &Configuration, - ) -> Result>, ServiceBuildError> { - match configuration.experimental_query_planner_mode { - QueryPlannerMode::Legacy => Ok(None), - QueryPlannerMode::New | QueryPlannerMode::Both => { - Ok(Some(Self::rust(schema, configuration)?)) - } - QueryPlannerMode::BothBestEffort | QueryPlannerMode::NewBestEffort => { - match Self::rust(schema, configuration) { - Ok(planner) => Ok(Some(planner)), - Err(error) => { - tracing::info!("Falling back to the legacy query planner: {error}"); - Ok(None) - } - } - } - } - } - fn rust( schema: &Schema, configuration: &Configuration, @@ -197,55 +124,17 @@ impl PlannerMode { Ok(Arc::new(result.map_err(ServiceBuildError::QpInitError)?)) } - async fn js_planner( - sdl: &str, - configuration: &Configuration, - old_js_planner: &Option>>, - ) -> Result>, ServiceBuildError> { - let query_planner_configuration = configuration.js_query_planner_config(); - let planner = match old_js_planner { - None => Planner::new(sdl.to_owned(), query_planner_configuration).await?, - Some(old_planner) => { - old_planner - .update(sdl.to_owned(), query_planner_configuration) - .await? - } - }; - Ok(Arc::new(planner)) - } - async fn plan( &self, doc: &ParsedDocument, - filtered_query: String, operation: Option, plan_options: PlanOptions, // Initialization code that needs mutable access to the plan, // before we potentially share it in Arc with a background thread // for "both" mode. init_query_plan_root_node: impl Fn(&mut PlanNode) -> Result<(), ValidationErrors>, - ) -> Result, QueryPlannerError> { + ) -> Result { match self { - PlannerMode::Js(js) => { - let start = Instant::now(); - - let result = js.plan(filtered_query, operation, plan_options).await; - - let elapsed = start.elapsed().as_secs_f64(); - metric_query_planning_plan_duration(JS_QP_MODE, elapsed); - - let mut success = result - .map_err(QueryPlannerError::RouterBridgeError)? - .into_result() - .map_err(PlanErrors::from)?; - - if let Some(root_node) = &mut success.data.query_plan.node { - // Arc freshly deserialized from Deno should be unique, so this doesn’t clone: - let root_node = Arc::make_mut(root_node); - init_query_plan_root_node(root_node)?; - } - Ok(success) - } PlannerMode::Rust(rust_planner) => { let doc = doc.clone(); let rust_planner = rust_planner.clone(); @@ -294,95 +183,28 @@ impl PlannerMode { init_query_plan_root_node(node)?; } - // Dummy value overwritten below in `BrigeQueryPlanner::plan` - let usage_reporting = UsageReporting { - stats_report_key: Default::default(), - referenced_fields_by_type: Default::default(), - }; - - Ok(PlanSuccess { - usage_reporting, - data: QueryPlanResult { - formatted_query_plan: Some(Arc::new(plan.to_string())), - query_plan: QueryPlan { - node: root_node.map(Arc::new), - }, - evaluated_plan_count: plan - .statistics - .evaluated_plan_count - .clone() - .into_inner() as u64, + Ok(QueryPlanResult { + formatted_query_plan: Some(Arc::new(plan.to_string())), + query_plan: QueryPlan { + node: root_node.map(Arc::new), }, + evaluated_plan_count: plan.statistics.evaluated_plan_count.clone().into_inner() + as u64, }) } - PlannerMode::Both { js, rust } => { - let start = Instant::now(); - - let result = js - .plan(filtered_query, operation.clone(), plan_options.clone()) - .await; - - let elapsed = start.elapsed().as_secs_f64(); - metric_query_planning_plan_duration(JS_QP_MODE, elapsed); - - let mut js_result = result - .map_err(QueryPlannerError::RouterBridgeError)? - .into_result() - .map_err(PlanErrors::from); - - if let Ok(success) = &mut js_result { - if let Some(root_node) = &mut success.data.query_plan.node { - // Arc freshly deserialized from Deno should be unique, so this doesn’t clone: - let root_node = Arc::make_mut(root_node); - init_query_plan_root_node(root_node)?; - } - } - - let query_plan_options = QueryPlanOptions { - override_conditions: plan_options.override_conditions, - }; - BothModeComparisonJob { - rust_planner: rust.clone(), - js_duration: elapsed, - document: doc.executable.clone(), - operation_name: operation, - // Exclude usage reporting from the Result sent for comparison - js_result: js_result - .as_ref() - .map(|success| success.data.clone()) - .map_err(|e| e.errors.clone()), - plan_options: query_plan_options, - } - .schedule(); - - Ok(js_result?) - } } } async fn subgraphs( &self, ) -> Result>>, ServiceBuildError> { - let js = match self { - PlannerMode::Js(js) => js, - PlannerMode::Both { js, .. } => js, - PlannerMode::Rust(rust) => { - return Ok(rust - .subgraph_schemas() - .iter() - .map(|(name, schema)| (name.to_string(), Arc::new(schema.schema().clone()))) - .collect()) - } - }; - js.subgraphs() - .await? - .into_iter() - .map(|(name, schema_str)| { - let schema = apollo_compiler::Schema::parse_and_validate(schema_str, "") - .map_err(|errors| SchemaError::Validate(errors.into()))?; - Ok((name, Arc::new(schema))) - }) - .collect() + match self { + PlannerMode::Rust(rust) => Ok(rust + .subgraph_schemas() + .iter() + .map(|(name, schema)| (name.to_string(), Arc::new(schema.schema().clone()))) + .collect()), + } } } @@ -390,12 +212,9 @@ impl BridgeQueryPlanner { pub(crate) async fn new( schema: Arc, configuration: Arc, - old_js_planner: Option>>, - rust_planner: Option>, introspection_cache: Arc, ) -> Result { - let planner = - PlannerMode::new(&schema, &configuration, &old_js_planner, rust_planner).await?; + let planner = PlannerMode::Rust(PlannerMode::rust(&schema, &configuration)?); let subgraph_schemas = Arc::new(planner.subgraphs().await?); @@ -417,14 +236,6 @@ impl BridgeQueryPlanner { }) } - pub(crate) fn js_planner(&self) -> Option>> { - match &self.planner { - PlannerMode::Js(js) => Some(js.clone()), - PlannerMode::Both { js, .. } => Some(js.clone()), - PlannerMode::Rust(_) => None, - } - } - #[cfg(test)] pub(crate) fn schema(&self) -> Arc { self.schema.clone() @@ -489,90 +300,65 @@ impl BridgeQueryPlanner { doc: &ParsedDocument, query_metrics: OperationLimits, ) -> Result { - let plan_success = self + let plan_result = self .planner - .plan( - doc, - filtered_query.clone(), - operation.clone(), - plan_options, - |root_node| { - root_node.init_parsed_operations_and_hash_subqueries( - &self.subgraph_schemas, - &self.schema.raw_sdl, - )?; - root_node.extract_authorization_metadata(self.schema.supergraph_schema(), &key); - Ok(()) - }, - ) + .plan(doc, operation.clone(), plan_options, |root_node| { + root_node.init_parsed_operations_and_hash_subqueries( + &self.subgraph_schemas, + &self.schema.raw_sdl, + )?; + root_node.extract_authorization_metadata(self.schema.supergraph_schema(), &key); + Ok(()) + }) .await?; + let QueryPlanResult { + query_plan: QueryPlan { node }, + formatted_query_plan, + evaluated_plan_count, + } = plan_result; + + // If the query is filtered, we want to generate the signature using the original query and generate the + // reference using the filtered query. To do this, we need to re-parse the original query here. + let signature_doc = if original_query != filtered_query { + Query::parse_document( + &original_query, + operation.clone().as_deref(), + &self.schema, + &self.configuration, + ) + .unwrap_or(doc.clone()) + } else { + doc.clone() + }; - match plan_success { - PlanSuccess { - data: - QueryPlanResult { - query_plan: QueryPlan { node: Some(node) }, - formatted_query_plan, - evaluated_plan_count, - }, - mut usage_reporting, - } => { - // If the query is filtered, we want to generate the signature using the original query and generate the - // reference using the filtered query. To do this, we need to re-parse the original query here. - let signature_doc = if original_query != filtered_query { - Query::parse_document( - &original_query, - operation.clone().as_deref(), - &self.schema, - &self.configuration, - ) - .unwrap_or(doc.clone()) - } else { - doc.clone() - }; - - u64_histogram!( - "apollo.router.query_planning.plan.evaluated_plans", - "Number of query plans evaluated for a query before choosing the best one", - evaluated_plan_count - ); - - let generated_usage_reporting = generate_usage_reporting( - &signature_doc.executable, - &doc.executable, - &operation, - self.schema.supergraph_schema(), - &self.signature_normalization_algorithm, - ); - - usage_reporting.stats_report_key = - generated_usage_reporting.result.stats_report_key; - usage_reporting.referenced_fields_by_type = - generated_usage_reporting.result.referenced_fields_by_type; - - Ok(QueryPlannerContent::Plan { - plan: Arc::new(super::QueryPlan { - usage_reporting: Arc::new(usage_reporting), - root: node, - formatted_query_plan, - query: Arc::new(selections), - query_metrics, - estimated_size: Default::default(), - }), - }) - } - #[cfg_attr(feature = "failfast", allow(unused_variables))] - PlanSuccess { - data: - QueryPlanResult { - query_plan: QueryPlan { node: None }, - .. - }, - usage_reporting, - } => { - failfast_debug!("empty query plan"); - Err(QueryPlannerError::EmptyPlan(usage_reporting)) - } + let usage_reporting = generate_usage_reporting( + &signature_doc.executable, + &doc.executable, + &operation, + self.schema.supergraph_schema(), + &self.signature_normalization_algorithm, + ); + + if let Some(node) = node { + u64_histogram!( + "apollo.router.query_planning.plan.evaluated_plans", + "Number of query plans evaluated for a query before choosing the best one", + evaluated_plan_count + ); + + Ok(QueryPlannerContent::Plan { + plan: Arc::new(super::QueryPlan { + usage_reporting: Arc::new(usage_reporting), + root: node, + formatted_query_plan, + query: Arc::new(selections), + query_metrics, + estimated_size: Default::default(), + }), + }) + } else { + failfast_debug!("empty query plan"); + Err(QueryPlannerError::EmptyPlan(usage_reporting)) } } } @@ -782,18 +568,12 @@ impl BridgeQueryPlanner { // Note: Reexported under `apollo_router::_private` #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct QueryPlanResult { +pub(crate) struct QueryPlanResult { pub(super) formatted_query_plan: Option>, pub(super) query_plan: QueryPlan, pub(super) evaluated_plan_count: u64, } -impl QueryPlanResult { - pub fn formatted_query_plan(&self) -> Option<&str> { - self.formatted_query_plan.as_deref().map(String::as_str) - } -} - #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] /// The root query plan container. @@ -834,7 +614,6 @@ pub(crate) fn metric_rust_qp_init(init_error_kind: Option<&'static str>) { mod tests { use serde_json::json; use test_log::test; - use tower::Service; use tower::ServiceExt; use super::*; @@ -878,34 +657,24 @@ mod tests { #[test(tokio::test)] async fn federation_versions() { - async { - let sdl = include_str!("../testdata/minimal_fed1_supergraph.graphql"); - let config = Arc::default(); - let schema = Schema::parse(sdl, &config).unwrap(); - let introspection = Arc::new(IntrospectionCache::new(&config)); - let _planner = - BridgeQueryPlanner::new(schema.into(), config, None, None, introspection) - .await - .unwrap(); - - assert_gauge!( - "apollo.router.supergraph.federation", - 1, - federation.version = 1 - ); - } - .with_metrics() - .await; + let sdl = include_str!("../testdata/minimal_fed1_supergraph.graphql"); + let config = Arc::default(); + let schema = Schema::parse(sdl, &config).unwrap(); + let introspection = Arc::new(IntrospectionCache::new(&config)); + let error = BridgeQueryPlanner::new(schema.into(), config, introspection) + .await + .err() + .expect("expected error for fed1 supergraph"); + assert_eq!(error.to_string(), "failed to initialize the query planner: Supergraphs composed with federation version 1 are not supported. Please recompose your supergraph with federation version 2 or greater"); async { let sdl = include_str!("../testdata/minimal_supergraph.graphql"); let config = Arc::default(); let schema = Schema::parse(sdl, &config).unwrap(); let introspection = Arc::new(IntrospectionCache::new(&config)); - let _planner = - BridgeQueryPlanner::new(schema.into(), config, None, None, introspection) - .await - .unwrap(); + let _planner = BridgeQueryPlanner::new(schema.into(), config, introspection) + .await + .unwrap(); assert_gauge!( "apollo.router.supergraph.federation", @@ -926,8 +695,6 @@ mod tests { let planner = BridgeQueryPlanner::new( schema.clone(), Default::default(), - None, - None, Arc::new(IntrospectionCache::new(&config)), ) .await @@ -1033,8 +800,6 @@ mod tests { let planner = BridgeQueryPlanner::new( schema.into(), configuration.clone(), - None, - None, Arc::new(IntrospectionCache::new(&configuration)), ) .await @@ -1347,8 +1112,6 @@ mod tests { let planner = BridgeQueryPlanner::new( schema.into(), configuration.clone(), - None, - None, Arc::new(IntrospectionCache::new(&configuration)), ) .await @@ -1375,30 +1138,6 @@ mod tests { .await } - #[tokio::test] - async fn test_both_mode() { - let mut harness = crate::TestHarness::builder() - // auth is not relevant here, but supergraph.graphql uses join/v0.1 - // which is not supported by the Rust query planner - .schema(include_str!("../../tests/fixtures/supergraph-auth.graphql")) - .configuration_json(serde_json::json!({ - "experimental_query_planner_mode": "both", - })) - .unwrap() - .build_supergraph() - .await - .unwrap(); - - let request = supergraph::Request::fake_builder() - .query("{ topProducts { name }}") - .build() - .unwrap(); - let mut response = harness.ready().await.unwrap().call(request).await.unwrap(); - assert!(response.response.status().is_success()); - let response = response.next_response().await.unwrap(); - assert!(response.errors.is_empty()); - } - #[tokio::test] async fn test_rust_mode_subgraph_operation_serialization() { let subgraph_queries = Arc::new(tokio::sync::Mutex::new(String::new())); @@ -1407,10 +1146,6 @@ mod tests { // auth is not relevant here, but supergraph.graphql uses join/v0.1 // which is not supported by the Rust query planner .schema(include_str!("../../tests/fixtures/supergraph-auth.graphql")) - .configuration_json(serde_json::json!({ - "experimental_query_planner_mode": "new", - })) - .unwrap() .subgraph_hook(move |_name, _default| { let subgraph_queries = Arc::clone(&subgraph_queries); tower::service_fn(move |request: subgraph::Request| { @@ -1462,15 +1197,6 @@ mod tests { f64, "planner" = "rust" ); - - let start = Instant::now(); - let elapsed = start.elapsed().as_secs_f64(); - metric_query_planning_plan_duration(JS_QP_MODE, elapsed); - assert_histogram_exists!( - "apollo.router.query_planning.plan.duration", - f64, - "planner" = "js" - ); } #[test] diff --git a/apollo-router/src/query_planner/bridge_query_planner_pool.rs b/apollo-router/src/query_planner/bridge_query_planner_pool.rs index 5246b79901..ac0e554b06 100644 --- a/apollo-router/src/query_planner/bridge_query_planner_pool.rs +++ b/apollo-router/src/query_planner/bridge_query_planner_pool.rs @@ -1,249 +1,59 @@ use std::collections::HashMap; -use std::num::NonZeroUsize; -use std::sync::atomic::AtomicU64; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::Mutex; use std::task::Poll; use std::time::Instant; use apollo_compiler::validation::Valid; -use async_channel::bounded; -use async_channel::Sender; use futures::future::BoxFuture; -use opentelemetry::metrics::MeterProvider; use opentelemetry::metrics::ObservableGauge; -use opentelemetry::metrics::Unit; -use router_bridge::planner::Planner; -use tokio::sync::oneshot; -use tokio::task::JoinSet; -use tower::Service; -use tower::ServiceExt; use super::bridge_query_planner::BridgeQueryPlanner; -use super::QueryPlanResult; -use crate::configuration::QueryPlannerMode; use crate::error::QueryPlannerError; use crate::error::ServiceBuildError; use crate::introspection::IntrospectionCache; -use crate::metrics::meter_provider; -use crate::query_planner::PlannerMode; use crate::services::QueryPlannerRequest; use crate::services::QueryPlannerResponse; use crate::spec::Schema; use crate::Configuration; -static CHANNEL_SIZE: usize = 1_000; - #[derive(Clone)] pub(crate) struct BridgeQueryPlannerPool { - js_planners: Vec>>, pool_mode: PoolMode, schema: Arc, subgraph_schemas: Arc>>>, compute_jobs_queue_size_gauge: Arc>>>, - v8_heap_used: Arc, - v8_heap_used_gauge: Arc>>>, - v8_heap_total: Arc, - v8_heap_total_gauge: Arc>>>, introspection_cache: Arc, } +// TODO: remove #[derive(Clone)] enum PoolMode { - Pool { - sender: Sender<( - QueryPlannerRequest, - oneshot::Sender>, - )>, - pool_size_gauge: Arc>>>, - }, - PassThrough { - delegate: BridgeQueryPlanner, - }, + PassThrough { delegate: BridgeQueryPlanner }, } impl BridgeQueryPlannerPool { pub(crate) async fn new( - old_js_planners: Vec>>, schema: Arc, configuration: Arc, - size: NonZeroUsize, ) -> Result { - let rust_planner = PlannerMode::maybe_rust(&schema, &configuration)?; - - let mut old_js_planners_iterator = old_js_planners.into_iter(); - // All query planners in the pool now share the same introspection cache. // This allows meaningful gauges, and it makes sense that queries should be cached across all planners. let introspection_cache = Arc::new(IntrospectionCache::new(&configuration)); - let pool_mode; - let js_planners; - let subgraph_schemas; - if let QueryPlannerMode::New = configuration.experimental_query_planner_mode { - let old_planner = old_js_planners_iterator.next(); - let delegate = BridgeQueryPlanner::new( - schema.clone(), - configuration, - old_planner, - rust_planner, - introspection_cache.clone(), - ) - .await?; - js_planners = delegate.js_planner().into_iter().collect::>(); - subgraph_schemas = delegate.subgraph_schemas(); - pool_mode = PoolMode::PassThrough { delegate } - } else { - let mut join_set = JoinSet::new(); - let (sender, receiver) = bounded::<( - QueryPlannerRequest, - oneshot::Sender>, - )>(CHANNEL_SIZE); - - for _ in 0..size.into() { - let schema = schema.clone(); - let configuration = configuration.clone(); - let rust_planner = rust_planner.clone(); - let introspection_cache = introspection_cache.clone(); - - let old_planner = old_js_planners_iterator.next(); - join_set.spawn(async move { - BridgeQueryPlanner::new( - schema, - configuration, - old_planner, - rust_planner, - introspection_cache, - ) - .await - }); - } - - let mut bridge_query_planners = Vec::new(); - - while let Some(task_result) = join_set.join_next().await { - let bridge_query_planner = - task_result.map_err(|e| ServiceBuildError::ServiceError(Box::new(e)))??; - bridge_query_planners.push(bridge_query_planner); - } - - subgraph_schemas = bridge_query_planners - .first() - .ok_or_else(|| { - ServiceBuildError::QueryPlannerError(QueryPlannerError::PoolProcessing( - "There should be at least 1 Query Planner service in pool".to_string(), - )) - })? - .subgraph_schemas(); - - js_planners = bridge_query_planners - .iter() - .filter_map(|p| p.js_planner()) - .collect(); - - for mut planner in bridge_query_planners.into_iter() { - let receiver = receiver.clone(); - - tokio::spawn(async move { - while let Ok((request, res_sender)) = receiver.recv().await { - let svc = match planner.ready().await { - Ok(svc) => svc, - Err(e) => { - let _ = res_sender.send(Err(e)); - - continue; - } - }; - - let res = svc.call(request).await; - - let _ = res_sender.send(res); - } - }); - } - pool_mode = PoolMode::Pool { - sender, - pool_size_gauge: Default::default(), - } - } - let v8_heap_used: Arc = Default::default(); - let v8_heap_total: Arc = Default::default(); - - // initialize v8 metrics - if let Some(bridge_query_planner) = js_planners.first().cloned() { - Self::get_v8_metrics( - bridge_query_planner, - v8_heap_used.clone(), - v8_heap_total.clone(), - ) - .await; - } + let delegate = + BridgeQueryPlanner::new(schema.clone(), configuration, introspection_cache.clone()) + .await?; Ok(Self { - js_planners, - pool_mode, + subgraph_schemas: delegate.subgraph_schemas(), + pool_mode: PoolMode::PassThrough { delegate }, schema, - subgraph_schemas, compute_jobs_queue_size_gauge: Default::default(), - v8_heap_used, - v8_heap_used_gauge: Default::default(), - v8_heap_total, - v8_heap_total_gauge: Default::default(), introspection_cache, }) } - fn create_pool_size_gauge(&self) { - if let PoolMode::Pool { - sender, - pool_size_gauge, - } = &self.pool_mode - { - let sender = sender.clone(); - let meter = meter_provider().meter("apollo/router"); - let gauge = meter - .u64_observable_gauge("apollo.router.query_planning.queued") - .with_description("Number of queries waiting to be planned") - .with_unit(Unit::new("query")) - .with_callback(move |m| m.observe(sender.len() as u64, &[])) - .init(); - *pool_size_gauge.lock().expect("lock poisoned") = Some(gauge); - } - } - - fn create_heap_used_gauge(&self) -> ObservableGauge { - let meter = meter_provider().meter("apollo/router"); - let current_heap_used_for_gauge = self.v8_heap_used.clone(); - let heap_used_gauge = meter - .u64_observable_gauge("apollo.router.v8.heap.used") - .with_description("V8 heap used, in bytes") - .with_unit(Unit::new("By")) - .with_callback(move |i| { - i.observe(current_heap_used_for_gauge.load(Ordering::SeqCst), &[]) - }) - .init(); - heap_used_gauge - } - - fn create_heap_total_gauge(&self) -> ObservableGauge { - let meter = meter_provider().meter("apollo/router"); - let current_heap_total_for_gauge = self.v8_heap_total.clone(); - let heap_total_gauge = meter - .u64_observable_gauge("apollo.router.v8.heap.total") - .with_description("V8 heap total, in bytes") - .with_unit(Unit::new("By")) - .with_callback(move |i| { - i.observe(current_heap_total_for_gauge.load(Ordering::SeqCst), &[]) - }) - .init(); - heap_total_gauge - } - - pub(crate) fn js_planners(&self) -> Vec>> { - self.js_planners.clone() - } - pub(crate) fn schema(&self) -> Arc { self.schema.clone() } @@ -254,18 +64,6 @@ impl BridgeQueryPlannerPool { self.subgraph_schemas.clone() } - async fn get_v8_metrics( - planner: Arc>, - v8_heap_used: Arc, - v8_heap_total: Arc, - ) { - let metrics = planner.get_heap_statistics().await; - if let Ok(metrics) = metrics { - v8_heap_used.store(metrics.heap_used, Ordering::SeqCst); - v8_heap_total.store(metrics.heap_total, Ordering::SeqCst); - } - } - pub(super) fn activate(&self) { // Gauges MUST be initialized after a meter provider is created. // When a hot reload happens this means that the gauges must be re-initialized. @@ -273,11 +71,6 @@ impl BridgeQueryPlannerPool { .compute_jobs_queue_size_gauge .lock() .expect("lock poisoned") = Some(crate::compute_job::create_queue_size_gauge()); - self.create_pool_size_gauge(); - *self.v8_heap_used_gauge.lock().expect("lock poisoned") = - Some(self.create_heap_used_gauge()); - *self.v8_heap_total_gauge.lock().expect("lock poisoned") = - Some(self.create_heap_total_gauge()); self.introspection_cache.activate(); } } @@ -291,42 +84,17 @@ impl tower::Service for BridgeQueryPlannerPool { fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> Poll> { if crate::compute_job::is_full() { - return Poll::Pending; - } - match &self.pool_mode { - PoolMode::Pool { sender, .. } if sender.is_full() => Poll::Ready(Err( - QueryPlannerError::PoolProcessing("query plan queue is full".into()), - )), - _ => Poll::Ready(Ok(())), + Poll::Pending + } else { + Poll::Ready(Ok(())) } } fn call(&mut self, req: QueryPlannerRequest) -> Self::Future { let pool_mode = self.pool_mode.clone(); - - let get_metrics_future = - if let Some(bridge_query_planner) = self.js_planners.first().cloned() { - Some(Self::get_v8_metrics( - bridge_query_planner, - self.v8_heap_used.clone(), - self.v8_heap_total.clone(), - )) - } else { - None - }; - Box::pin(async move { let start; let res = match pool_mode { - PoolMode::Pool { sender, .. } => { - let (response_sender, response_receiver) = oneshot::channel(); - start = Instant::now(); - let _ = sender.send((req, response_sender)).await; - - response_receiver - .await - .map_err(|_| QueryPlannerError::UnhandledPlannerResult)? - } PoolMode::PassThrough { mut delegate } => { start = Instant::now(); delegate.call(req).await @@ -339,85 +107,7 @@ impl tower::Service for BridgeQueryPlannerPool { start.elapsed().as_secs_f64() ); - if let Some(f) = get_metrics_future { - // execute in a separate task to avoid blocking the request - tokio::task::spawn(f); - } - res }) } } - -#[cfg(test)] - -mod tests { - use opentelemetry_sdk::metrics::data::Gauge; - use router_bridge::planner::PlanOptions; - - use super::*; - use crate::metrics::FutureMetricsExt; - use crate::plugins::authorization::CacheKeyMetadata; - use crate::spec::Query; - use crate::Context; - - #[tokio::test] - async fn test_v8_metrics() { - let sdl = include_str!("../testdata/supergraph.graphql"); - let config = Arc::default(); - let schema = Arc::new(Schema::parse(sdl, &config).unwrap()); - - async move { - let mut pool = BridgeQueryPlannerPool::new( - Vec::new(), - schema.clone(), - config.clone(), - NonZeroUsize::new(2).unwrap(), - ) - .await - .unwrap(); - pool.activate(); - let query = "query { me { name } }".to_string(); - - let doc = Query::parse_document(&query, None, &schema, &config).unwrap(); - let context = Context::new(); - context - .extensions() - .with_lock(|mut lock| lock.insert(doc.clone())); - - pool.call(QueryPlannerRequest::new( - query, - None, - doc, - CacheKeyMetadata::default(), - PlanOptions::default(), - )) - .await - .unwrap(); - - let metrics = crate::metrics::collect_metrics(); - let heap_used = metrics.find("apollo.router.v8.heap.used").unwrap(); - let heap_total = metrics.find("apollo.router.v8.heap.total").unwrap(); - - println!( - "got heap_used: {:?}, heap_total: {:?}", - heap_used - .data - .as_any() - .downcast_ref::>() - .unwrap() - .data_points[0] - .value, - heap_total - .data - .as_any() - .downcast_ref::>() - .unwrap() - .data_points[0] - .value - ); - } - .with_metrics() - .await; - } -} diff --git a/apollo-router/src/query_planner/caching_query_planner.rs b/apollo-router/src/query_planner/caching_query_planner.rs index 7b1f8c5279..e5ea4941e4 100644 --- a/apollo-router/src/query_planner/caching_query_planner.rs +++ b/apollo-router/src/query_planner/caching_query_planner.rs @@ -10,10 +10,6 @@ use indexmap::IndexMap; use query_planner::QueryPlannerPlugin; use rand::seq::SliceRandom; use rand::thread_rng; -use router_bridge::planner::PlanOptions; -use router_bridge::planner::Planner; -use router_bridge::planner::QueryPlannerConfig; -use router_bridge::planner::UsageReporting; use sha2::Digest; use sha2::Sha256; use tower::BoxError; @@ -23,6 +19,7 @@ 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; use crate::cache::storage::ValueType; @@ -36,11 +33,11 @@ use crate::plugins::progressive_override::LABELS_TO_OVERRIDE_KEY; use crate::plugins::telemetry::utils::Timer; use crate::query_planner::fetch::SubgraphSchemas; use crate::query_planner::BridgeQueryPlannerPool; -use crate::query_planner::QueryPlanResult; use crate::services::layers::persisted_queries::PersistedQueryLayer; use crate::services::layers::query_analysis::ParsedDocument; use crate::services::layers::query_analysis::QueryAnalysisLayer; use crate::services::query_planner; +use crate::services::query_planner::PlanOptions; use crate::services::QueryPlannerContent; use crate::services::QueryPlannerRequest; use crate::services::QueryPlannerResponse; @@ -54,14 +51,6 @@ pub(crate) type InMemoryCachePlanner = InMemoryCache>>; pub(crate) const APOLLO_OPERATION_ID: &str = "apollo_operation_id"; -#[derive(Debug, Clone, Hash)] -pub(crate) enum ConfigMode { - Rust(Arc), - Both(Arc), - BothBestEffort(Arc), - Js(Arc), -} - /// A query planner wrapper that caches results. /// /// The query planner performs LRU caching. @@ -121,37 +110,7 @@ where AuthorizationPlugin::enable_directives(configuration, &schema).unwrap_or(false); let mut hasher = StructHasher::new(); - match configuration.experimental_query_planner_mode { - crate::configuration::QueryPlannerMode::New => { - "PLANNER-NEW".hash(&mut hasher); - ConfigMode::Rust(Arc::new(configuration.rust_query_planner_config())) - .hash(&mut hasher); - } - crate::configuration::QueryPlannerMode::Legacy => { - "PLANNER-LEGACY".hash(&mut hasher); - ConfigMode::Js(Arc::new(configuration.js_query_planner_config())).hash(&mut hasher); - } - crate::configuration::QueryPlannerMode::Both => { - "PLANNER-BOTH".hash(&mut hasher); - ConfigMode::Both(Arc::new(configuration.js_query_planner_config())) - .hash(&mut hasher); - ConfigMode::Rust(Arc::new(configuration.rust_query_planner_config())) - .hash(&mut hasher); - } - crate::configuration::QueryPlannerMode::BothBestEffort => { - "PLANNER-BOTH-BEST-EFFORT".hash(&mut hasher); - ConfigMode::BothBestEffort(Arc::new(configuration.js_query_planner_config())) - .hash(&mut hasher); - ConfigMode::Rust(Arc::new(configuration.rust_query_planner_config())) - .hash(&mut hasher); - } - crate::configuration::QueryPlannerMode::NewBestEffort => { - "PLANNER-NEW-BEST-EFFORT".hash(&mut hasher); - ConfigMode::Js(Arc::new(configuration.js_query_planner_config())).hash(&mut hasher); - ConfigMode::Rust(Arc::new(configuration.rust_query_planner_config())) - .hash(&mut hasher); - } - }; + configuration.rust_query_planner_config().hash(&mut hasher); let config_mode_hash = Arc::new(QueryHash(hasher.finalize())); Ok(Self { @@ -378,10 +337,6 @@ where } impl CachingQueryPlanner { - pub(crate) fn js_planners(&self) -> Vec>> { - self.delegate.js_planners() - } - pub(crate) fn subgraph_schemas( &self, ) -> Arc>>> { @@ -717,12 +672,11 @@ impl ValueType for Result> { mod tests { use mockall::mock; use mockall::predicate::*; - use router_bridge::planner::UsageReporting; use test_log::test; use tower::Service; use super::*; - use crate::error::PlanErrors; + use crate::apollo_studio_interop::UsageReporting; use crate::json_ext::Object; use crate::query_planner::QueryPlan; use crate::spec::Query; @@ -769,15 +723,10 @@ mod tests { let mut delegate = MockMyQueryPlanner::new(); delegate.expect_clone().returning(|| { let mut planner = MockMyQueryPlanner::new(); - planner.expect_sync_call().times(0..2).returning(|_| { - Err(QueryPlannerError::from(PlanErrors { - errors: Default::default(), - usage_reporting: UsageReporting { - stats_report_key: "this is a test key".to_string(), - referenced_fields_by_type: Default::default(), - }, - })) - }); + planner + .expect_sync_call() + .times(0..2) + .returning(|_| Err(QueryPlannerError::UnhandledPlannerResult)); planner }); diff --git a/apollo-router/src/query_planner/dual_query_planner.rs b/apollo-router/src/query_planner/dual_query_planner.rs deleted file mode 100644 index 3996d4f22c..0000000000 --- a/apollo-router/src/query_planner/dual_query_planner.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Running two query planner implementations and comparing their results - -use std::sync::Arc; -use std::sync::OnceLock; -use std::time::Instant; - -use apollo_compiler::validation::Valid; -use apollo_compiler::ExecutableDocument; -use apollo_compiler::Name; -use apollo_federation::error::FederationError; -use apollo_federation::query_plan::query_planner::QueryPlanOptions; -use apollo_federation::query_plan::query_planner::QueryPlanner; - -use crate::error::format_bridge_errors; -use crate::query_planner::bridge_query_planner::metric_query_planning_plan_duration; -use crate::query_planner::bridge_query_planner::JS_QP_MODE; -use crate::query_planner::bridge_query_planner::RUST_QP_MODE; -use crate::query_planner::convert::convert_root_query_plan_node; -use crate::query_planner::plan_compare::diff_plan; -use crate::query_planner::plan_compare::opt_plan_node_matches; -use crate::query_planner::QueryPlanResult; - -/// Jobs are dropped if this many are already queued -const QUEUE_SIZE: usize = 10; -const WORKER_THREAD_COUNT: usize = 1; - -pub(crate) struct BothModeComparisonJob { - pub(crate) rust_planner: Arc, - pub(crate) js_duration: f64, - pub(crate) document: Arc>, - pub(crate) operation_name: Option, - pub(crate) js_result: Result>>, - pub(crate) plan_options: QueryPlanOptions, -} - -type Queue = crossbeam_channel::Sender; - -static QUEUE: OnceLock = OnceLock::new(); - -fn queue() -> &'static Queue { - QUEUE.get_or_init(|| { - let (sender, receiver) = crossbeam_channel::bounded::(QUEUE_SIZE); - for _ in 0..WORKER_THREAD_COUNT { - let job_receiver = receiver.clone(); - std::thread::spawn(move || { - for job in job_receiver { - job.execute() - } - }); - } - sender - }) -} - -impl BothModeComparisonJob { - pub(crate) fn schedule(self) { - // We use a bounded queue: try_send returns an error when full. This is fine. - // We prefer dropping some comparison jobs and only gathering some of the data - // rather than consume too much resources. - // - // Either way we move on and let this thread continue proceed with the query plan from JS. - let _ = queue().try_send(self).is_err(); - } - - fn execute(self) { - let start = Instant::now(); - - let rust_result = self - .operation_name - .as_deref() - .map(|n| Name::new(n).map_err(FederationError::from)) - .transpose() - .and_then(|operation| { - self.rust_planner - .build_query_plan(&self.document, operation, self.plan_options) - }); - - let elapsed = start.elapsed().as_secs_f64(); - metric_query_planning_plan_duration(RUST_QP_MODE, elapsed); - - metric_query_planning_plan_both_comparison_duration(RUST_QP_MODE, elapsed); - metric_query_planning_plan_both_comparison_duration(JS_QP_MODE, self.js_duration); - - let name = self.operation_name.as_deref(); - let operation_desc = if let Ok(operation) = self.document.operations.get(name) { - if let Some(parsed_name) = &operation.name { - format!(" in {} `{parsed_name}`", operation.operation_type) - } else { - format!(" in anonymous {}", operation.operation_type) - } - } else { - String::new() - }; - - let is_matched; - match (&self.js_result, &rust_result) { - (Err(js_errors), Ok(_)) => { - tracing::warn!( - "JS query planner error{operation_desc}: {}", - format_bridge_errors(js_errors) - ); - is_matched = false; - } - (Ok(_), Err(rust_error)) => { - tracing::warn!("Rust query planner error{operation_desc}: {}", rust_error); - is_matched = false; - } - (Err(_), Err(_)) => { - is_matched = true; - } - - (Ok(js_plan), Ok(rust_plan)) => { - let js_root_node = &js_plan.query_plan.node; - let rust_root_node = convert_root_query_plan_node(rust_plan); - let match_result = opt_plan_node_matches(js_root_node, &rust_root_node); - is_matched = match_result.is_ok(); - match match_result { - Ok(_) => tracing::trace!("JS and Rust query plans match{operation_desc}! 🎉"), - Err(err) => { - tracing::debug!("JS v.s. Rust query plan mismatch{operation_desc}"); - tracing::debug!("{}", err.full_description()); - tracing::debug!( - "Diff of formatted plans:\n{}", - diff_plan(js_plan, rust_plan) - ); - tracing::trace!("JS query plan Debug: {js_root_node:#?}"); - tracing::trace!("Rust query plan Debug: {rust_root_node:#?}"); - } - } - } - } - - u64_counter!( - "apollo.router.operations.query_planner.both", - "Comparing JS v.s. Rust query plans", - 1, - "generation.is_matched" = is_matched, - "generation.js_error" = self.js_result.is_err(), - "generation.rust_error" = rust_result.is_err() - ); - } -} - -pub(crate) fn metric_query_planning_plan_both_comparison_duration( - planner: &'static str, - elapsed: f64, -) { - f64_histogram!( - "apollo.router.operations.query_planner.both.duration", - "Comparing JS v.s. Rust query plan duration.", - elapsed, - "planner" = planner - ); -} - -#[cfg(test)] -mod tests { - use std::time::Instant; - - use super::*; - - #[test] - fn test_metric_query_planning_plan_both_comparison_duration() { - let start = Instant::now(); - let elapsed = start.elapsed().as_secs_f64(); - metric_query_planning_plan_both_comparison_duration(RUST_QP_MODE, elapsed); - assert_histogram_exists!( - "apollo.router.operations.query_planner.both.duration", - f64, - "planner" = "rust" - ); - - let start = Instant::now(); - let elapsed = start.elapsed().as_secs_f64(); - metric_query_planning_plan_both_comparison_duration(JS_QP_MODE, elapsed); - assert_histogram_exists!( - "apollo.router.operations.query_planner.both.duration", - f64, - "planner" = "js" - ); - } -} diff --git a/apollo-router/src/query_planner/mod.rs b/apollo-router/src/query_planner/mod.rs index fb912e1d16..d26237c712 100644 --- a/apollo-router/src/query_planner/mod.rs +++ b/apollo-router/src/query_planner/mod.rs @@ -14,12 +14,10 @@ pub(crate) mod bridge_query_planner; mod bridge_query_planner_pool; mod caching_query_planner; mod convert; -pub(crate) mod dual_query_planner; mod execution; pub(crate) mod fetch; mod labeler; mod plan; -pub(crate) mod plan_compare; pub(crate) mod rewrites; mod selection; mod subgraph_context; diff --git a/apollo-router/src/query_planner/plan.rs b/apollo-router/src/query_planner/plan.rs index f0e5763358..076c47080c 100644 --- a/apollo-router/src/query_planner/plan.rs +++ b/apollo-router/src/query_planner/plan.rs @@ -3,14 +3,13 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use apollo_compiler::validation::Valid; -use router_bridge::planner::PlanOptions; -use router_bridge::planner::UsageReporting; use serde::Deserialize; use serde::Serialize; pub(crate) use self::fetch::OperationKind; use super::fetch; use super::subscription::SubscriptionNode; +use crate::apollo_studio_interop::UsageReporting; use crate::cache::estimate_size; use crate::configuration::Batching; use crate::error::CacheResolverError; @@ -21,6 +20,7 @@ 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; diff --git a/apollo-router/src/query_planner/plan_compare.rs b/apollo-router/src/query_planner/plan_compare.rs deleted file mode 100644 index 7367ac2caa..0000000000 --- a/apollo-router/src/query_planner/plan_compare.rs +++ /dev/null @@ -1,1303 +0,0 @@ -// Semantic comparison of JS and Rust query plans - -use std::borrow::Borrow; -use std::collections::hash_map::HashMap; -use std::fmt::Write; -use std::hash::DefaultHasher; -use std::hash::Hash; -use std::hash::Hasher; - -use apollo_compiler::ast; -use apollo_compiler::Name; -use apollo_compiler::Node; -use apollo_federation::query_plan::QueryPlan as NativeQueryPlan; - -use super::convert::convert_root_query_plan_node; -use super::fetch::FetchNode; -use super::fetch::SubgraphOperation; -use super::rewrites::DataRewrite; -use super::selection::Selection; -use super::subscription::SubscriptionNode; -use super::DeferredNode; -use super::FlattenNode; -use super::PlanNode; -use super::Primary; -use super::QueryPlanResult; -use crate::json_ext::Path; -use crate::json_ext::PathElement; - -//================================================================================================== -// Public interface - -pub struct MatchFailure { - description: String, - backtrace: std::backtrace::Backtrace, -} - -impl MatchFailure { - pub fn description(&self) -> String { - self.description.clone() - } - - pub fn full_description(&self) -> String { - format!("{}\n\nBacktrace:\n{}", self.description, self.backtrace) - } - - fn new(description: String) -> MatchFailure { - MatchFailure { - description, - backtrace: std::backtrace::Backtrace::force_capture(), - } - } - - fn add_description(self: MatchFailure, description: &str) -> MatchFailure { - MatchFailure { - description: format!("{}\n{}", self.description, description), - backtrace: self.backtrace, - } - } -} - -macro_rules! check_match { - ($pred:expr) => { - if !$pred { - return Err(MatchFailure::new(format!( - "mismatch at {}", - stringify!($pred) - ))); - } - }; -} - -macro_rules! check_match_eq { - ($a:expr, $b:expr) => { - if $a != $b { - let message = format!( - "mismatch between {} and {}:\nleft: {:?}\nright: {:?}", - stringify!($a), - stringify!($b), - $a, - $b - ); - return Err(MatchFailure::new(message)); - } - }; -} - -// Note: Reexported under `apollo_router::_private` -pub fn plan_matches( - js_plan: &QueryPlanResult, - rust_plan: &NativeQueryPlan, -) -> Result<(), MatchFailure> { - let js_root_node = &js_plan.query_plan.node; - let rust_root_node = convert_root_query_plan_node(rust_plan); - opt_plan_node_matches(js_root_node, &rust_root_node) -} - -// Note: Reexported under `apollo_router::_private` -pub fn diff_plan(js_plan: &QueryPlanResult, rust_plan: &NativeQueryPlan) -> String { - let js_root_node = &js_plan.query_plan.node; - let rust_root_node = convert_root_query_plan_node(rust_plan); - - match (js_root_node, rust_root_node) { - (None, None) => String::from(""), - (None, Some(rust)) => { - let rust = &format!("{rust:#?}"); - let differences = diff::lines("", rust); - render_diff(&differences) - } - (Some(js), None) => { - let js = &format!("{js:#?}"); - let differences = diff::lines(js, ""); - render_diff(&differences) - } - (Some(js), Some(rust)) => { - let rust = &format!("{rust:#?}"); - let js = &format!("{js:#?}"); - let differences = diff::lines(js, rust); - render_diff(&differences) - } - } -} - -// Note: Reexported under `apollo_router::_private` -pub fn render_diff(differences: &[diff::Result<&str>]) -> String { - let mut output = String::new(); - for diff_line in differences { - match diff_line { - diff::Result::Left(l) => { - let trimmed = l.trim(); - if !trimmed.starts_with('#') && !trimmed.is_empty() { - writeln!(&mut output, "-{l}").expect("write will never fail"); - } else { - writeln!(&mut output, " {l}").expect("write will never fail"); - } - } - diff::Result::Both(l, _) => { - writeln!(&mut output, " {l}").expect("write will never fail"); - } - diff::Result::Right(r) => { - let trimmed = r.trim(); - if trimmed != "---" && !trimmed.is_empty() { - writeln!(&mut output, "+{r}").expect("write will never fail"); - } - } - } - } - output -} - -//================================================================================================== -// Vec comparison functions - -fn vec_matches(this: &[T], other: &[T], item_matches: impl Fn(&T, &T) -> bool) -> bool { - this.len() == other.len() - && std::iter::zip(this, other).all(|(this, other)| item_matches(this, other)) -} - -fn vec_matches_result( - this: &[T], - other: &[T], - item_matches: impl Fn(&T, &T) -> Result<(), MatchFailure>, -) -> Result<(), MatchFailure> { - check_match_eq!(this.len(), other.len()); - std::iter::zip(this, other) - .enumerate() - .try_fold((), |_acc, (index, (this, other))| { - item_matches(this, other) - .map_err(|err| err.add_description(&format!("under item[{}]", index))) - })?; - Ok(()) -} - -fn vec_matches_sorted(this: &[T], other: &[T]) -> bool { - let mut this_sorted = this.to_owned(); - let mut other_sorted = other.to_owned(); - this_sorted.sort(); - other_sorted.sort(); - vec_matches(&this_sorted, &other_sorted, T::eq) -} - -fn vec_matches_sorted_by( - this: &[T], - other: &[T], - compare: impl Fn(&T, &T) -> std::cmp::Ordering, - item_matches: impl Fn(&T, &T) -> bool, -) -> bool { - let mut this_sorted = this.to_owned(); - let mut other_sorted = other.to_owned(); - this_sorted.sort_by(&compare); - other_sorted.sort_by(&compare); - vec_matches(&this_sorted, &other_sorted, item_matches) -} - -fn vec_matches_result_sorted_by( - this: &[T], - other: &[T], - compare: impl Fn(&T, &T) -> std::cmp::Ordering, - item_matches: impl Fn(&T, &T) -> Result<(), MatchFailure>, -) -> Result<(), MatchFailure> { - check_match_eq!(this.len(), other.len()); - let mut this_sorted = this.to_owned(); - let mut other_sorted = other.to_owned(); - this_sorted.sort_by(&compare); - other_sorted.sort_by(&compare); - std::iter::zip(&this_sorted, &other_sorted) - .try_fold((), |_acc, (this, other)| item_matches(this, other))?; - Ok(()) -} - -// `this` vector includes `other` vector as a set -fn vec_includes_as_set(this: &[T], other: &[T], item_matches: impl Fn(&T, &T) -> bool) -> bool { - other.iter().all(|other_node| { - this.iter() - .any(|this_node| item_matches(this_node, other_node)) - }) -} - -// performs a set comparison, ignoring order -fn vec_matches_as_set(this: &[T], other: &[T], item_matches: impl Fn(&T, &T) -> bool) -> bool { - // Set-inclusion test in both directions - this.len() == other.len() - && vec_includes_as_set(this, other, &item_matches) - && vec_includes_as_set(other, this, &item_matches) -} - -// Forward/reverse mappings from one Vec items (indices) to another. -type VecMapping = (HashMap, HashMap); - -// performs a set comparison, ignoring order -// and returns a mapping from `this` to `other`. -fn vec_matches_as_set_with_mapping( - this: &[T], - other: &[T], - item_matches: impl Fn(&T, &T) -> bool, -) -> VecMapping { - // Set-inclusion test in both directions - // - record forward/reverse mapping from this items <-> other items for reporting mismatches - let mut forward_map: HashMap = HashMap::new(); - let mut reverse_map: HashMap = HashMap::new(); - for (this_pos, this_node) in this.iter().enumerate() { - if let Some(other_pos) = other - .iter() - .position(|other_node| item_matches(this_node, other_node)) - { - forward_map.insert(this_pos, other_pos); - reverse_map.insert(other_pos, this_pos); - } - } - for (other_pos, other_node) in other.iter().enumerate() { - if reverse_map.contains_key(&other_pos) { - continue; - } - if let Some(this_pos) = this - .iter() - .position(|this_node| item_matches(this_node, other_node)) - { - forward_map.insert(this_pos, other_pos); - reverse_map.insert(other_pos, this_pos); - } - } - (forward_map, reverse_map) -} - -// Returns a formatted mismatch message and an optional pair of mismatched positions if the pair -// are the only remaining unmatched items. -fn format_mismatch_as_set( - this_len: usize, - other_len: usize, - forward_map: &HashMap, - reverse_map: &HashMap, -) -> Result<(String, Option<(usize, usize)>), std::fmt::Error> { - let mut ret = String::new(); - let buf = &mut ret; - write!(buf, "- mapping from left to right: [")?; - let mut this_missing_pos = None; - for this_pos in 0..this_len { - if this_pos != 0 { - write!(buf, ", ")?; - } - if let Some(other_pos) = forward_map.get(&this_pos) { - write!(buf, "{}", other_pos)?; - } else { - this_missing_pos = Some(this_pos); - write!(buf, "?")?; - } - } - writeln!(buf, "]")?; - - write!(buf, "- left-over on the right: [")?; - let mut other_missing_count = 0; - let mut other_missing_pos = None; - for other_pos in 0..other_len { - if reverse_map.get(&other_pos).is_none() { - if other_missing_count != 0 { - write!(buf, ", ")?; - } - other_missing_count += 1; - other_missing_pos = Some(other_pos); - write!(buf, "{}", other_pos)?; - } - } - write!(buf, "]")?; - let unmatched_pair = if let (Some(this_missing_pos), Some(other_missing_pos)) = - (this_missing_pos, other_missing_pos) - { - if this_len == 1 + forward_map.len() && other_len == 1 + reverse_map.len() { - // Special case: There are only one missing item on each side. They are supposed to - // match each other. - Some((this_missing_pos, other_missing_pos)) - } else { - None - } - } else { - None - }; - Ok((ret, unmatched_pair)) -} - -fn vec_matches_result_as_set( - this: &[T], - other: &[T], - item_matches: impl Fn(&T, &T) -> Result<(), MatchFailure>, -) -> Result { - // Set-inclusion test in both directions - // - record forward/reverse mapping from this items <-> other items for reporting mismatches - let (forward_map, reverse_map) = - vec_matches_as_set_with_mapping(this, other, |a, b| item_matches(a, b).is_ok()); - if forward_map.len() == this.len() && reverse_map.len() == other.len() { - Ok((forward_map, reverse_map)) - } else { - // report mismatch - let Ok((message, unmatched_pair)) = - format_mismatch_as_set(this.len(), other.len(), &forward_map, &reverse_map) - else { - // Exception: Unable to format mismatch report => fallback to most generic message - return Err(MatchFailure::new( - "mismatch at vec_matches_result_as_set (failed to format mismatched sets)" - .to_string(), - )); - }; - if let Some(unmatched_pair) = unmatched_pair { - // found a unique pair to report => use that pair's error message - let Err(err) = item_matches(&this[unmatched_pair.0], &other[unmatched_pair.1]) else { - // Exception: Unable to format unique pair mismatch error => fallback to overall report - return Err(MatchFailure::new(format!( - "mismatched sets (failed to format unique pair mismatch error):\n{}", - message - ))); - }; - Err(err.add_description(&format!( - "under a sole unmatched pair ({} -> {}) in a set comparison", - unmatched_pair.0, unmatched_pair.1 - ))) - } else { - Err(MatchFailure::new(format!("mismatched sets:\n{}", message))) - } - } -} - -//================================================================================================== -// PlanNode comparison functions - -fn option_to_string(name: Option) -> String { - name.map_or_else(|| "".to_string(), |name| name.to_string()) -} - -fn plan_node_matches(this: &PlanNode, other: &PlanNode) -> Result<(), MatchFailure> { - match (this, other) { - (PlanNode::Sequence { nodes: this }, PlanNode::Sequence { nodes: other }) => { - vec_matches_result(this, other, plan_node_matches) - .map_err(|err| err.add_description("under Sequence node"))?; - } - (PlanNode::Parallel { nodes: this }, PlanNode::Parallel { nodes: other }) => { - vec_matches_result_as_set(this, other, plan_node_matches) - .map_err(|err| err.add_description("under Parallel node"))?; - } - (PlanNode::Fetch(this), PlanNode::Fetch(other)) => { - fetch_node_matches(this, other).map_err(|err| { - err.add_description(&format!( - "under Fetch node (operation name: {})", - option_to_string(this.operation_name.as_ref()) - )) - })?; - } - (PlanNode::Flatten(this), PlanNode::Flatten(other)) => { - flatten_node_matches(this, other).map_err(|err| { - err.add_description(&format!("under Flatten node (path: {})", this.path)) - })?; - } - ( - PlanNode::Defer { primary, deferred }, - PlanNode::Defer { - primary: other_primary, - deferred: other_deferred, - }, - ) => { - defer_primary_node_matches(primary, other_primary)?; - vec_matches_result(deferred, other_deferred, deferred_node_matches)?; - } - ( - PlanNode::Subscription { primary, rest }, - PlanNode::Subscription { - primary: other_primary, - rest: other_rest, - }, - ) => { - subscription_primary_matches(primary, other_primary)?; - opt_plan_node_matches(rest, other_rest) - .map_err(|err| err.add_description("under Subscription"))?; - } - ( - PlanNode::Condition { - condition, - if_clause, - else_clause, - }, - PlanNode::Condition { - condition: other_condition, - if_clause: other_if_clause, - else_clause: other_else_clause, - }, - ) => { - check_match_eq!(condition, other_condition); - opt_plan_node_matches(if_clause, other_if_clause) - .map_err(|err| err.add_description("under Condition node (if_clause)"))?; - opt_plan_node_matches(else_clause, other_else_clause) - .map_err(|err| err.add_description("under Condition node (else_clause)"))?; - } - _ => { - return Err(MatchFailure::new(format!( - "mismatched plan node types\nleft: {:?}\nright: {:?}", - this, other - ))) - } - }; - Ok(()) -} - -pub(crate) fn opt_plan_node_matches( - this: &Option>, - other: &Option>, -) -> Result<(), MatchFailure> { - match (this, other) { - (None, None) => Ok(()), - (None, Some(_)) | (Some(_), None) => Err(MatchFailure::new(format!( - "mismatch at opt_plan_node_matches\nleft: {:?}\nright: {:?}", - this.is_some(), - other.is_some() - ))), - (Some(this), Some(other)) => plan_node_matches(this.borrow(), other.borrow()), - } -} - -fn fetch_node_matches(this: &FetchNode, other: &FetchNode) -> Result<(), MatchFailure> { - let FetchNode { - service_name, - requires, - variable_usages, - operation, - // ignored: - // reordered parallel fetches may have different names - operation_name: _, - operation_kind, - id, - input_rewrites, - output_rewrites, - context_rewrites, - // ignored - schema_aware_hash: _, - // ignored: - // when running in comparison mode, the rust plan node does not have - // the attached cache key metadata for authorisation, since the rust plan is - // not going to be the one being executed. - authorization: _, - } = this; - - check_match_eq!(*service_name, other.service_name); - check_match_eq!(*operation_kind, other.operation_kind); - check_match_eq!(*id, other.id); - check_match!(same_requires(requires, &other.requires)); - check_match!(vec_matches_sorted(variable_usages, &other.variable_usages)); - check_match!(same_rewrites(input_rewrites, &other.input_rewrites)); - check_match!(same_rewrites(output_rewrites, &other.output_rewrites)); - check_match!(same_rewrites(context_rewrites, &other.context_rewrites)); - operation_matches(operation, &other.operation)?; - Ok(()) -} - -fn subscription_primary_matches( - this: &SubscriptionNode, - other: &SubscriptionNode, -) -> Result<(), MatchFailure> { - let SubscriptionNode { - service_name, - variable_usages, - operation, - operation_name: _, // ignored (reordered parallel fetches may have different names) - operation_kind, - input_rewrites, - output_rewrites, - } = this; - check_match_eq!(*service_name, other.service_name); - check_match_eq!(*operation_kind, other.operation_kind); - check_match!(vec_matches_sorted(variable_usages, &other.variable_usages)); - check_match!(same_rewrites(input_rewrites, &other.input_rewrites)); - check_match!(same_rewrites(output_rewrites, &other.output_rewrites)); - operation_matches(operation, &other.operation)?; - Ok(()) -} - -fn defer_primary_node_matches(this: &Primary, other: &Primary) -> Result<(), MatchFailure> { - let Primary { subselection, node } = this; - opt_document_string_matches(subselection, &other.subselection) - .map_err(|err| err.add_description("under defer primary subselection"))?; - opt_plan_node_matches(node, &other.node) - .map_err(|err| err.add_description("under defer primary plan node")) -} - -fn deferred_node_matches(this: &DeferredNode, other: &DeferredNode) -> Result<(), MatchFailure> { - let DeferredNode { - depends, - label, - query_path, - subselection, - node, - } = this; - - check_match_eq!(*depends, other.depends); - check_match_eq!(*label, other.label); - check_match_eq!(*query_path, other.query_path); - opt_document_string_matches(subselection, &other.subselection) - .map_err(|err| err.add_description("under deferred subselection"))?; - opt_plan_node_matches(node, &other.node) - .map_err(|err| err.add_description("under deferred node")) -} - -fn flatten_node_matches(this: &FlattenNode, other: &FlattenNode) -> Result<(), MatchFailure> { - let FlattenNode { path, node } = this; - check_match!(same_path(path, &other.path)); - plan_node_matches(node, &other.node) -} - -fn same_path(this: &Path, other: &Path) -> bool { - // Ignore the empty key root from the JS query planner - match this.0.split_first() { - Some((PathElement::Key(k, type_conditions), rest)) - if k.is_empty() && type_conditions.is_none() => - { - vec_matches(rest, &other.0, same_path_element) - } - _ => vec_matches(&this.0, &other.0, same_path_element), - } -} - -fn same_path_element(this: &PathElement, other: &PathElement) -> bool { - match (this, other) { - (PathElement::Index(this), PathElement::Index(other)) => this == other, - (PathElement::Fragment(this), PathElement::Fragment(other)) => this == other, - ( - PathElement::Key(this_key, this_type_conditions), - PathElement::Key(other_key, other_type_conditions), - ) => { - this_key == other_key - && same_path_condition(this_type_conditions, other_type_conditions) - } - ( - PathElement::Flatten(this_type_conditions), - PathElement::Flatten(other_type_conditions), - ) => same_path_condition(this_type_conditions, other_type_conditions), - _ => false, - } -} - -fn same_path_condition(this: &Option>, other: &Option>) -> bool { - match (this, other) { - (Some(this), Some(other)) => vec_matches_sorted(this, other), - (None, None) => true, - _ => false, - } -} - -// Copied and modified from `apollo_federation::operation::SelectionKey` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum SelectionKey { - Field { - /// The field alias (if specified) or field name in the resulting selection set. - response_name: Name, - directives: ast::DirectiveList, - }, - FragmentSpread { - /// The name of the fragment. - fragment_name: Name, - directives: ast::DirectiveList, - }, - InlineFragment { - /// The optional type condition of the fragment. - type_condition: Option, - directives: ast::DirectiveList, - }, -} - -fn get_selection_key(selection: &Selection) -> SelectionKey { - match selection { - Selection::Field(field) => SelectionKey::Field { - response_name: field.response_name().clone(), - directives: Default::default(), - }, - Selection::InlineFragment(fragment) => SelectionKey::InlineFragment { - type_condition: fragment.type_condition.clone(), - directives: Default::default(), - }, - } -} - -fn hash_value(x: &T) -> u64 { - let mut hasher = DefaultHasher::new(); - x.hash(&mut hasher); - hasher.finish() -} - -fn hash_selection_key(selection: &Selection) -> u64 { - hash_value(&get_selection_key(selection)) -} - -// Note: This `Selection` struct is a limited version used for the `requires` field. -fn same_selection(x: &Selection, y: &Selection) -> bool { - match (x, y) { - (Selection::Field(x), Selection::Field(y)) => { - x.name == y.name - && x.alias == y.alias - && match (&x.selections, &y.selections) { - (Some(x), Some(y)) => same_selection_set_sorted(x, y), - (None, None) => true, - _ => false, - } - } - (Selection::InlineFragment(x), Selection::InlineFragment(y)) => { - x.type_condition == y.type_condition - && same_selection_set_sorted(&x.selections, &y.selections) - } - _ => false, - } -} - -fn same_selection_set_sorted(x: &[Selection], y: &[Selection]) -> bool { - fn sorted_by_selection_key(s: &[Selection]) -> Vec<&Selection> { - let mut sorted: Vec<&Selection> = s.iter().collect(); - sorted.sort_by_key(|x| hash_selection_key(x)); - sorted - } - - if x.len() != y.len() { - return false; - } - sorted_by_selection_key(x) - .into_iter() - .zip(sorted_by_selection_key(y)) - .all(|(x, y)| same_selection(x, y)) -} - -fn same_requires(x: &[Selection], y: &[Selection]) -> bool { - vec_matches_as_set(x, y, same_selection) -} - -fn same_rewrites(x: &Option>, y: &Option>) -> bool { - match (x, y) { - (None, None) => true, - (Some(x), Some(y)) => vec_matches_as_set(x, y, |a, b| a == b), - _ => false, - } -} - -fn operation_matches( - this: &SubgraphOperation, - other: &SubgraphOperation, -) -> Result<(), MatchFailure> { - document_str_matches(this.as_serialized(), other.as_serialized()) -} - -// Compare operation document strings such as query or just selection set. -fn document_str_matches(this: &str, other: &str) -> Result<(), MatchFailure> { - let this_ast = match ast::Document::parse(this, "this_operation.graphql") { - Ok(document) => document, - Err(_) => { - return Err(MatchFailure::new( - "Failed to parse this operation".to_string(), - )); - } - }; - let other_ast = match ast::Document::parse(other, "other_operation.graphql") { - Ok(document) => document, - Err(_) => { - return Err(MatchFailure::new( - "Failed to parse other operation".to_string(), - )); - } - }; - same_ast_document(&this_ast, &other_ast) -} - -fn opt_document_string_matches( - this: &Option, - other: &Option, -) -> Result<(), MatchFailure> { - match (this, other) { - (None, None) => Ok(()), - (Some(this_sel), Some(other_sel)) => document_str_matches(this_sel, other_sel), - _ => Err(MatchFailure::new(format!( - "mismatched at opt_document_string_matches\nleft: {:?}\nright: {:?}", - this, other - ))), - } -} - -//================================================================================================== -// AST comparison functions - -fn same_ast_document(x: &ast::Document, y: &ast::Document) -> Result<(), MatchFailure> { - fn split_definitions( - doc: &ast::Document, - ) -> ( - Vec<&ast::OperationDefinition>, - Vec<&ast::FragmentDefinition>, - Vec<&ast::Definition>, - ) { - let mut operations: Vec<&ast::OperationDefinition> = Vec::new(); - let mut fragments: Vec<&ast::FragmentDefinition> = Vec::new(); - let mut others: Vec<&ast::Definition> = Vec::new(); - for def in doc.definitions.iter() { - match def { - ast::Definition::OperationDefinition(op) => operations.push(op), - ast::Definition::FragmentDefinition(frag) => fragments.push(frag), - _ => others.push(def), - } - } - (operations, fragments, others) - } - - let (x_ops, x_frags, x_others) = split_definitions(x); - let (y_ops, y_frags, y_others) = split_definitions(y); - - debug_assert!(x_others.is_empty(), "Unexpected definition types"); - debug_assert!(y_others.is_empty(), "Unexpected definition types"); - debug_assert!( - x_ops.len() == y_ops.len(), - "Different number of operation definitions" - ); - - check_match_eq!(x_frags.len(), y_frags.len()); - let mut fragment_map: HashMap = HashMap::new(); - // Assumption: x_frags and y_frags are topologically sorted. - // Thus, we can build the fragment name mapping in a single pass and compare - // fragment definitions using the mapping at the same time, since earlier fragments - // will never reference later fragments. - x_frags.iter().try_fold((), |_, x_frag| { - let y_frag = y_frags - .iter() - .find(|y_frag| same_ast_fragment_definition(x_frag, y_frag, &fragment_map).is_ok()); - if let Some(y_frag) = y_frag { - if x_frag.name != y_frag.name { - // record it only if they are not identical - fragment_map.insert(x_frag.name.clone(), y_frag.name.clone()); - } - Ok(()) - } else { - Err(MatchFailure::new(format!( - "mismatch: no matching fragment definition for {}", - x_frag.name - ))) - } - })?; - - check_match_eq!(x_ops.len(), y_ops.len()); - x_ops - .iter() - .zip(y_ops.iter()) - .try_fold((), |_, (x_op, y_op)| { - same_ast_operation_definition(x_op, y_op, &fragment_map) - .map_err(|err| err.add_description("under operation definition")) - })?; - Ok(()) -} - -fn same_ast_operation_definition( - x: &ast::OperationDefinition, - y: &ast::OperationDefinition, - fragment_map: &HashMap, -) -> Result<(), MatchFailure> { - // Note: Operation names are ignored, since parallel fetches may have different names. - check_match_eq!(x.operation_type, y.operation_type); - vec_matches_result_sorted_by( - &x.variables, - &y.variables, - |a, b| a.name.cmp(&b.name), - |a, b| same_variable_definition(a, b), - ) - .map_err(|err| err.add_description("under Variable definition"))?; - check_match_eq!(x.directives, y.directives); - check_match!(same_ast_selection_set_sorted( - &x.selection_set, - &y.selection_set, - fragment_map, - )); - Ok(()) -} - -// `x` may be coerced to `y`. -// - `x` should be a value from JS QP. -// - `y` should be a value from Rust QP. -// - Assume: x and y are already checked not equal. -// Due to coercion differences, we need to compare AST values with special cases. -fn ast_value_maybe_coerced_to(x: &ast::Value, y: &ast::Value) -> bool { - match (x, y) { - // Special case 1: JS QP may convert an enum value into string. - // - In this case, compare them as strings. - (ast::Value::String(ref x), ast::Value::Enum(ref y)) => { - if x == y.as_str() { - return true; - } - } - - // Special case 2: Rust QP expands a object value by filling in its - // default field values. - // - If the Rust QP object value subsumes the JS QP object value, consider it a match. - // - Assuming the Rust QP object value has only default field values. - // - Warning: This is an unsound heuristic. - (ast::Value::Object(ref x), ast::Value::Object(ref y)) => { - if vec_includes_as_set(y, x, |(yy_name, yy_val), (xx_name, xx_val)| { - xx_name == yy_name - && (xx_val == yy_val || ast_value_maybe_coerced_to(xx_val, yy_val)) - }) { - return true; - } - } - - // Special case 3: JS QP may convert string to int for custom scalars, while Rust doesn't. - // - Note: This conversion seems a bit difficult to implement in the `apollo-federation`'s - // `coerce_value` function, since IntValue's constructor is private to the crate. - (ast::Value::Int(ref x), ast::Value::String(ref y)) => { - if x.as_str() == y { - return true; - } - } - - // Recurse into list items. - (ast::Value::List(ref x), ast::Value::List(ref y)) => { - if vec_matches(x, y, |xx, yy| { - xx == yy || ast_value_maybe_coerced_to(xx, yy) - }) { - return true; - } - } - - _ => {} // otherwise, fall through - } - false -} - -// Use this function, instead of `VariableDefinition`'s `PartialEq` implementation, -// due to known differences. -fn same_variable_definition( - x: &ast::VariableDefinition, - y: &ast::VariableDefinition, -) -> Result<(), MatchFailure> { - check_match_eq!(x.name, y.name); - check_match_eq!(x.ty, y.ty); - if x.default_value != y.default_value { - if let (Some(x), Some(y)) = (&x.default_value, &y.default_value) { - if ast_value_maybe_coerced_to(x, y) { - return Ok(()); - } - } - - return Err(MatchFailure::new(format!( - "mismatch between default values:\nleft: {:?}\nright: {:?}", - x.default_value, y.default_value - ))); - } - check_match_eq!(x.directives, y.directives); - Ok(()) -} - -fn same_ast_fragment_definition( - x: &ast::FragmentDefinition, - y: &ast::FragmentDefinition, - fragment_map: &HashMap, -) -> Result<(), MatchFailure> { - // Note: Fragment names at definitions are ignored. - check_match_eq!(x.type_condition, y.type_condition); - check_match_eq!(x.directives, y.directives); - check_match!(same_ast_selection_set_sorted( - &x.selection_set, - &y.selection_set, - fragment_map, - )); - Ok(()) -} - -fn same_ast_argument_value(x: &ast::Value, y: &ast::Value) -> bool { - x == y || ast_value_maybe_coerced_to(x, y) -} - -fn same_ast_argument(x: &ast::Argument, y: &ast::Argument) -> bool { - x.name == y.name && same_ast_argument_value(&x.value, &y.value) -} - -fn same_ast_arguments(x: &[Node], y: &[Node]) -> bool { - vec_matches_sorted_by( - x, - y, - |a, b| a.name.cmp(&b.name), - |a, b| same_ast_argument(a, b), - ) -} - -fn same_directives(x: &ast::DirectiveList, y: &ast::DirectiveList) -> bool { - vec_matches_sorted_by( - x, - y, - |a, b| a.name.cmp(&b.name), - |a, b| a.name == b.name && same_ast_arguments(&a.arguments, &b.arguments), - ) -} - -fn get_ast_selection_key( - selection: &ast::Selection, - fragment_map: &HashMap, -) -> SelectionKey { - match selection { - ast::Selection::Field(field) => SelectionKey::Field { - response_name: field.response_name().clone(), - directives: field.directives.clone(), - }, - ast::Selection::FragmentSpread(fragment) => SelectionKey::FragmentSpread { - fragment_name: fragment_map - .get(&fragment.fragment_name) - .unwrap_or(&fragment.fragment_name) - .clone(), - directives: fragment.directives.clone(), - }, - ast::Selection::InlineFragment(fragment) => SelectionKey::InlineFragment { - type_condition: fragment.type_condition.clone(), - directives: fragment.directives.clone(), - }, - } -} - -fn same_ast_selection( - x: &ast::Selection, - y: &ast::Selection, - fragment_map: &HashMap, -) -> bool { - match (x, y) { - (ast::Selection::Field(x), ast::Selection::Field(y)) => { - x.name == y.name - && x.alias == y.alias - && same_ast_arguments(&x.arguments, &y.arguments) - && same_directives(&x.directives, &y.directives) - && same_ast_selection_set_sorted(&x.selection_set, &y.selection_set, fragment_map) - } - (ast::Selection::FragmentSpread(x), ast::Selection::FragmentSpread(y)) => { - let mapped_fragment_name = fragment_map - .get(&x.fragment_name) - .unwrap_or(&x.fragment_name); - *mapped_fragment_name == y.fragment_name - && same_directives(&x.directives, &y.directives) - } - (ast::Selection::InlineFragment(x), ast::Selection::InlineFragment(y)) => { - x.type_condition == y.type_condition - && same_directives(&x.directives, &y.directives) - && same_ast_selection_set_sorted(&x.selection_set, &y.selection_set, fragment_map) - } - _ => false, - } -} - -fn hash_ast_selection_key(selection: &ast::Selection, fragment_map: &HashMap) -> u64 { - hash_value(&get_ast_selection_key(selection, fragment_map)) -} - -// Selections are sorted and compared after renaming x's fragment spreads according to the -// fragment_map. -fn same_ast_selection_set_sorted( - x: &[ast::Selection], - y: &[ast::Selection], - fragment_map: &HashMap, -) -> bool { - fn sorted_by_selection_key<'a>( - s: &'a [ast::Selection], - fragment_map: &HashMap, - ) -> Vec<&'a ast::Selection> { - let mut sorted: Vec<&ast::Selection> = s.iter().collect(); - sorted.sort_by_key(|x| hash_ast_selection_key(x, fragment_map)); - sorted - } - - if x.len() != y.len() { - return false; - } - let x_sorted = sorted_by_selection_key(x, fragment_map); // Map fragment spreads - let y_sorted = sorted_by_selection_key(y, &Default::default()); // Don't map fragment spreads - x_sorted - .into_iter() - .zip(y_sorted) - .all(|(x, y)| same_ast_selection(x, y, fragment_map)) -} - -//================================================================================================== -// Unit tests - -#[cfg(test)] -mod ast_comparison_tests { - use super::*; - - #[test] - fn test_query_variable_decl_order() { - let op_x = r#"query($qv2: String!, $qv1: Int!) { x(arg1: $qv1, arg2: $qv2) }"#; - let op_y = r#"query($qv1: Int!, $qv2: String!) { x(arg1: $qv1, arg2: $qv2) }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_query_variable_decl_enum_value_coercion() { - // Note: JS QP converts enum default values into strings. - let op_x = r#"query($qv1: E! = "default_value") { x(arg1: $qv1) }"#; - let op_y = r#"query($qv1: E! = default_value) { x(arg1: $qv1) }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_query_variable_decl_object_value_coercion_empty_case() { - // Note: Rust QP expands empty object default values by filling in its default field - // values. - let op_x = r#"query($qv1: T! = {}) { x(arg1: $qv1) }"#; - let op_y = - r#"query($qv1: T! = { field1: true, field2: "default_value" }) { x(arg1: $qv1) }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_query_variable_decl_object_value_coercion_non_empty_case() { - // Note: Rust QP expands an object default values by filling in its default field values. - let op_x = r#"query($qv1: T! = {field1: true}) { x(arg1: $qv1) }"#; - let op_y = - r#"query($qv1: T! = { field1: true, field2: "default_value" }) { x(arg1: $qv1) }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_query_variable_decl_list_of_object_value_coercion() { - // Testing a combination of list and object value coercion. - let op_x = r#"query($qv1: [T!]! = [{}]) { x(arg1: $qv1) }"#; - let op_y = - r#"query($qv1: [T!]! = [{field1: true, field2: "default_value"}]) { x(arg1: $qv1) }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_entities_selection_order() { - let op_x = r#" - query subgraph1__1($representations: [_Any!]!) { - _entities(representations: $representations) { x { w } y } - } - "#; - let op_y = r#" - query subgraph1__1($representations: [_Any!]!) { - _entities(representations: $representations) { y x { w } } - } - "#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_top_level_selection_order() { - let op_x = r#"{ x { w z } y }"#; - let op_y = r#"{ y x { z w } }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_fragment_definition_order() { - let op_x = r#"{ q { ...f1 ...f2 } } fragment f1 on T { x y } fragment f2 on T { w z }"#; - let op_y = r#"{ q { ...f1 ...f2 } } fragment f2 on T { w z } fragment f1 on T { x y }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_selection_argument_is_compared() { - let op_x = r#"{ x(arg1: "one") }"#; - let op_y = r#"{ x(arg1: "two") }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_err()); - } - - #[test] - fn test_selection_argument_order() { - let op_x = r#"{ x(arg1: "one", arg2: "two") }"#; - let op_y = r#"{ x(arg2: "two", arg1: "one") }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_selection_directive_order() { - let op_x = r#"{ x @include(if:true) @skip(if:false) }"#; - let op_y = r#"{ x @skip(if:false) @include(if:true) }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_string_to_id_coercion_difference() { - // JS QP coerces strings into integer for ID type, while Rust QP doesn't. - // This tests a special case that same_ast_document accepts this difference. - let op_x = r#"{ x(id: 123) }"#; - let op_y = r#"{ x(id: "123") }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_fragment_definition_different_names() { - let op_x = r#"{ q { ...f1 ...f2 } } fragment f1 on T { x y } fragment f2 on T { w z }"#; - let op_y = r#"{ q { ...g1 ...g2 } } fragment g1 on T { x y } fragment g2 on T { w z }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_fragment_definition_different_names_nested_1() { - // Nested fragments have the same name, only top-level fragments have different names. - let op_x = r#"{ q { ...f2 } } fragment f1 on T { x y } fragment f2 on T { z ...f1 }"#; - let op_y = r#"{ q { ...g2 } } fragment f1 on T { x y } fragment g2 on T { z ...f1 }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_fragment_definition_different_names_nested_2() { - // Nested fragments have different names. - let op_x = r#"{ q { ...f2 } } fragment f1 on T { x y } fragment f2 on T { z ...f1 }"#; - let op_y = r#"{ q { ...g2 } } fragment g1 on T { x y } fragment g2 on T { z ...g1 }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } - - #[test] - fn test_fragment_definition_different_names_nested_3() { - // Nested fragments have different names. - // Also, fragment definitions are in different order. - let op_x = r#"{ q { ...f2 ...f3 } } fragment f1 on T { x y } fragment f2 on T { z ...f1 } fragment f3 on T { w } "#; - let op_y = r#"{ q { ...g2 ...g3 } } fragment g1 on T { x y } fragment g2 on T { w } fragment g3 on T { z ...g1 }"#; - let ast_x = ast::Document::parse(op_x, "op_x").unwrap(); - let ast_y = ast::Document::parse(op_y, "op_y").unwrap(); - assert!(super::same_ast_document(&ast_x, &ast_y).is_ok()); - } -} - -#[cfg(test)] -mod qp_selection_comparison_tests { - use serde_json::json; - - use super::*; - - #[test] - fn test_requires_comparison_with_same_selection_key() { - let requires_json = json!([ - { - "kind": "InlineFragment", - "typeCondition": "T", - "selections": [ - { - "kind": "Field", - "name": "id", - }, - ] - }, - { - "kind": "InlineFragment", - "typeCondition": "T", - "selections": [ - { - "kind": "Field", - "name": "id", - }, - { - "kind": "Field", - "name": "job", - } - ] - }, - ]); - - // The only difference between requires1 and requires2 is the order of selections. - // But, their items all have the same SelectionKey. - let requires1: Vec = serde_json::from_value(requires_json).unwrap(); - let requires2: Vec = requires1.iter().rev().cloned().collect(); - - // `same_selection_set_sorted` fails to match, since it doesn't account for - // two items with the same SelectionKey but in different order. - assert!(!same_selection_set_sorted(&requires1, &requires2)); - // `same_requires` should succeed. - assert!(same_requires(&requires1, &requires2)); - } -} - -#[cfg(test)] -mod path_comparison_tests { - use serde_json::json; - - use super::*; - - macro_rules! matches_deserialized_path { - ($json:expr, $expected:expr) => { - let path: Path = serde_json::from_value($json).unwrap(); - assert_eq!(path, $expected); - }; - } - - #[test] - fn test_type_condition_deserialization() { - matches_deserialized_path!( - json!(["k"]), - Path(vec![PathElement::Key("k".to_string(), None)]) - ); - matches_deserialized_path!( - json!(["k|[A]"]), - Path(vec![PathElement::Key( - "k".to_string(), - Some(vec!["A".to_string()]) - )]) - ); - matches_deserialized_path!( - json!(["k|[A,B]"]), - Path(vec![PathElement::Key( - "k".to_string(), - Some(vec!["A".to_string(), "B".to_string()]) - )]) - ); - matches_deserialized_path!( - json!(["k|[]"]), - Path(vec![PathElement::Key("k".to_string(), Some(vec![]))]) - ); - } - - macro_rules! assert_path_match { - ($a:expr, $b:expr) => { - let legacy_path: Path = serde_json::from_value($a).unwrap(); - let native_path: Path = serde_json::from_value($b).unwrap(); - assert!(same_path(&legacy_path, &native_path)); - }; - } - - macro_rules! assert_path_differ { - ($a:expr, $b:expr) => { - let legacy_path: Path = serde_json::from_value($a).unwrap(); - let native_path: Path = serde_json::from_value($b).unwrap(); - assert!(!same_path(&legacy_path, &native_path)); - }; - } - - #[test] - fn test_same_path_basic() { - // Basic dis-equality tests. - assert_path_differ!(json!([]), json!(["a"])); - assert_path_differ!(json!(["a"]), json!(["b"])); - assert_path_differ!(json!(["a", "b"]), json!(["a", "b", "c"])); - } - - #[test] - fn test_same_path_ignore_empty_root_key() { - assert_path_match!(json!(["", "k|[A]", "v"]), json!(["k|[A]", "v"])); - } - - #[test] - fn test_same_path_distinguishes_empty_conditions_from_no_conditions() { - // Create paths that use no type conditions and empty type conditions - assert_path_differ!(json!(["k|[]", "v"]), json!(["k", "v"])); - } -} diff --git a/apollo-router/src/query_planner/selection.rs b/apollo-router/src/query_planner/selection.rs index cf58be1244..8550a8d655 100644 --- a/apollo-router/src/query_planner/selection.rs +++ b/apollo-router/src/query_planner/selection.rs @@ -39,13 +39,6 @@ pub(crate) struct Field { pub(crate) selections: Option>, } -impl Field { - // Mirroring `apollo_compiler::Field::response_name` - pub(crate) fn response_name(&self) -> &Name { - self.alias.as_ref().unwrap_or(&self.name) - } -} - /// An inline fragment. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap index 7b2e0c912f..cbac25c490 100644 --- a/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap +++ b/apollo-router/src/query_planner/snapshots/apollo_router__query_planner__bridge_query_planner__tests__plan_root.snap @@ -7,7 +7,7 @@ Fetch( service_name: "accounts", requires: [], variable_usages: [], - operation: "{me{name{first last}}}", + operation: "{ me { name { first last } } }", operation_name: None, operation_kind: Query, id: None, @@ -15,7 +15,7 @@ Fetch( output_rewrites: None, context_rewrites: None, schema_aware_hash: QueryHash( - "65e550250ef331b8dc49d9e2da8f4cd5add979720cbe83ba545a0f78ece8d329", + "16d9399fe60cb6dcc81f522fab6a54056e8b65b02095667b76f1cdd7048aab50", ), authorization: CacheKeyMetadata { is_authenticated: false, diff --git a/apollo-router/src/query_planner/tests.rs b/apollo-router/src/query_planner/tests.rs index 766ce9a715..b22204f7cd 100644 --- a/apollo-router/src/query_planner/tests.rs +++ b/apollo-router/src/query_planner/tests.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use apollo_compiler::name; use futures::StreamExt; use http::Method; -use router_bridge::planner::UsageReporting; use serde_json_bytes::json; use tokio_stream::wrappers::ReceiverStream; use tower::ServiceExt; @@ -18,6 +17,7 @@ use super::OperationKind; use super::PlanNode; use super::Primary; use super::QueryPlan; +use crate::apollo_studio_interop::UsageReporting; use crate::graphql; use crate::json_ext::Path; use crate::json_ext::PathElement; diff --git a/apollo-router/src/router_factory.rs b/apollo-router/src/router_factory.rs index d34eae8b45..46f4d37a7f 100644 --- a/apollo-router/src/router_factory.rs +++ b/apollo-router/src/router_factory.rs @@ -295,20 +295,10 @@ impl YamlRouterFactory { ) -> Result { let query_planner_span = tracing::info_span!("query_planner_creation"); // QueryPlannerService takes an UnplannedRequest and outputs PlannedRequest - let bridge_query_planner = BridgeQueryPlannerPool::new( - previous_supergraph - .as_ref() - .map(|router| router.js_planners()) - .unwrap_or_default(), - schema.clone(), - configuration.clone(), - configuration - .supergraph - .query_planning - .experimental_query_planner_parallelism()?, - ) - .instrument(query_planner_span) - .await?; + let bridge_query_planner = + BridgeQueryPlannerPool::new(schema.clone(), configuration.clone()) + .instrument(query_planner_span) + .await?; let schema_changed = previous_supergraph .map(|supergraph_creator| supergraph_creator.schema().raw_sdl == schema.raw_sdl) @@ -498,12 +488,9 @@ pub async fn create_test_service_factory_from_yaml(schema: &str, configuration: .await; assert_eq!( service.map(|_| ()).unwrap_err().to_string().as_str(), - r#"couldn't build Query Planner Service: couldn't instantiate query planner; invalid schema: schema validation errors: Unexpected error extracting subgraphs from the supergraph: this is either a bug, or the supergraph has been corrupted. + r#"failed to initialize the query planner: An internal error has occurred, please report this bug to Apollo. -Details: -Error: Cannot find type "Review" in subgraph "products" -caused by -"# +Details: Object field "Product.reviews"'s inner type "Review" does not refer to an existing output type."# ); } diff --git a/apollo-router/src/services/layers/query_analysis.rs b/apollo-router/src/services/layers/query_analysis.rs index 2e2fe77eff..fbafb49a51 100644 --- a/apollo-router/src/services/layers/query_analysis.rs +++ b/apollo-router/src/services/layers/query_analysis.rs @@ -11,11 +11,11 @@ use apollo_compiler::ExecutableDocument; use apollo_compiler::Node; use http::StatusCode; use lru::LruCache; -use router_bridge::planner::UsageReporting; use tokio::sync::Mutex; use crate::apollo_studio_interop::generate_extended_references; use crate::apollo_studio_interop::ExtendedReferenceStats; +use crate::apollo_studio_interop::UsageReporting; use crate::compute_job; use crate::context::OPERATION_KIND; use crate::context::OPERATION_NAME; diff --git a/apollo-router/src/services/query_planner.rs b/apollo-router/src/services/query_planner.rs index d23a0e2ace..ce4675f884 100644 --- a/apollo-router/src/services/query_planner.rs +++ b/apollo-router/src/services/query_planner.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use async_trait::async_trait; use derivative::Derivative; -use router_bridge::planner::PlanOptions; use serde::Deserialize; use serde::Serialize; use static_assertions::assert_impl_all; @@ -15,6 +14,14 @@ use crate::graphql; use crate::query_planner::QueryPlan; use crate::Context; +/// Options for planning a query +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)] +#[serde(rename_all = "camelCase")] +pub(crate) struct PlanOptions { + /// Which labels to override during query planning + pub(crate) override_conditions: Vec, +} + assert_impl_all!(Request: Send); /// [`Context`] for the request. #[derive(Derivative)] diff --git a/apollo-router/src/services/supergraph/service.rs b/apollo-router/src/services/supergraph/service.rs index b059c7aa07..5a057bb85c 100644 --- a/apollo-router/src/services/supergraph/service.rs +++ b/apollo-router/src/services/supergraph/service.rs @@ -12,8 +12,6 @@ use http::StatusCode; use indexmap::IndexMap; use opentelemetry::Key; use opentelemetry::KeyValue; -use router_bridge::planner::Planner; -use router_bridge::planner::UsageReporting; use tokio::sync::mpsc; use tokio::sync::mpsc::error::SendError; use tokio_stream::wrappers::ReceiverStream; @@ -26,6 +24,7 @@ use tracing::field; use tracing::Span; use tracing_futures::Instrument; +use crate::apollo_studio_interop::UsageReporting; use crate::batching::BatchQuery; use crate::configuration::Batching; use crate::configuration::PersistedQueriesPrewarmQueryPlanCache; @@ -49,7 +48,6 @@ use crate::query_planner::subscription::SUBSCRIPTION_EVENT_SPAN_NAME; use crate::query_planner::BridgeQueryPlannerPool; use crate::query_planner::CachingQueryPlanner; use crate::query_planner::InMemoryCachePlanner; -use crate::query_planner::QueryPlanResult; use crate::router_factory::create_plugins; use crate::router_factory::create_subgraph_services; use crate::services::execution::QueryPlan; @@ -928,10 +926,6 @@ impl SupergraphCreator { self.query_planner_service.previous_cache() } - pub(crate) fn js_planners(&self) -> Vec>> { - self.query_planner_service.js_planners() - } - pub(crate) async fn warm_up_query_planner( &mut self, query_parser: &QueryAnalysisLayer, diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml deleted file mode 100644 index 195306ed62..0000000000 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans -supergraph: - query_planning: - cache: - redis: - required_to_start: true - urls: - - redis://localhost:6379 - ttl: 10s - -experimental_query_planner_mode: new \ No newline at end of file diff --git a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml b/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml deleted file mode 100644 index 6f24cebc9a..0000000000 --- a/apollo-router/tests/integration/fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# This config updates the query plan options so that we can see if there is a different redis cache entry generted for query plans -supergraph: - query_planning: - cache: - redis: - required_to_start: true - urls: - - redis://localhost:6379 - ttl: 10s - experimental_reuse_query_fragments: true - generate_query_fragments: false -experimental_query_planner_mode: legacy \ No newline at end of file diff --git a/apollo-router/tests/integration/query_planner.rs b/apollo-router/tests/integration/query_planner.rs index d2834bfd84..179ca1df9a 100644 --- a/apollo-router/tests/integration/query_planner.rs +++ b/apollo-router/tests/integration/query_planner.rs @@ -6,47 +6,11 @@ use crate::integration::IntegrationTest; mod max_evaluated_plans; const PROMETHEUS_METRICS_CONFIG: &str = include_str!("telemetry/fixtures/prometheus.router.yaml"); -const LEGACY_QP: &str = "experimental_query_planner_mode: legacy"; -const NEW_QP: &str = "experimental_query_planner_mode: new"; -const BOTH_QP: &str = "experimental_query_planner_mode: both"; -const BOTH_BEST_EFFORT_QP: &str = "experimental_query_planner_mode: both_best_effort"; -const NEW_BEST_EFFORT_QP: &str = "experimental_query_planner_mode: new_best_effort"; - -#[tokio::test(flavor = "multi_thread")] -async fn fed1_schema_with_legacy_qp() { - let mut router = IntegrationTest::builder() - .config(LEGACY_QP) - .supergraph("../examples/graphql/supergraph-fed1.graphql") - .build() - .await; - router.start().await; - router.assert_started().await; - router.execute_default_query().await; - router.graceful_shutdown().await; -} #[tokio::test(flavor = "multi_thread")] async fn fed1_schema_with_new_qp() { let mut router = IntegrationTest::builder() - .config(NEW_QP) - .supergraph("../examples/graphql/supergraph-fed1.graphql") - .build() - .await; - router.start().await; - router - .assert_log_contains( - "could not create router: \ - failed to initialize the query planner: \ - Supergraphs composed with federation version 1 are not supported.", - ) - .await; - router.assert_shutdown().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn fed1_schema_with_both_qp() { - let mut router = IntegrationTest::builder() - .config(BOTH_QP) + .config("{}") // Default config .supergraph("../examples/graphql/supergraph-fed1.graphql") .build() .await; @@ -61,112 +25,10 @@ async fn fed1_schema_with_both_qp() { router.assert_shutdown().await; } -#[tokio::test(flavor = "multi_thread")] -async fn fed1_schema_with_both_best_effort_qp() { - let mut router = IntegrationTest::builder() - .config(BOTH_BEST_EFFORT_QP) - .supergraph("../examples/graphql/supergraph-fed1.graphql") - .build() - .await; - router.start().await; - router - .assert_log_contains( - "Falling back to the legacy query planner: \ - failed to initialize the query planner: \ - Supergraphs composed with federation version 1 are not supported. \ - Please recompose your supergraph with federation version 2 or greater", - ) - .await; - router.assert_started().await; - router.execute_default_query().await; - router.graceful_shutdown().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn fed1_schema_with_new_best_effort_qp() { - let mut router = IntegrationTest::builder() - .config(NEW_BEST_EFFORT_QP) - .supergraph("../examples/graphql/supergraph-fed1.graphql") - .build() - .await; - router.start().await; - router - .assert_log_contains( - "Falling back to the legacy query planner: \ - failed to initialize the query planner: \ - Supergraphs composed with federation version 1 are not supported. \ - Please recompose your supergraph with federation version 2 or greater", - ) - .await; - router.assert_started().await; - router.execute_default_query().await; - router.graceful_shutdown().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn fed1_schema_with_legacy_qp_reload_to_new_keep_previous_config() { - let config = format!("{PROMETHEUS_METRICS_CONFIG}\n{LEGACY_QP}"); - let mut router = IntegrationTest::builder() - .config(config) - .supergraph("../examples/graphql/supergraph-fed1.graphql") - .build() - .await; - router.start().await; - router.assert_started().await; - router.execute_default_query().await; - - let config = format!("{PROMETHEUS_METRICS_CONFIG}\n{NEW_QP}"); - router.update_config(&config).await; - router - .assert_log_contains("error while reloading, continuing with previous configuration") - .await; - router - .assert_metrics_contains( - r#"apollo_router_lifecycle_query_planner_init_total{init_error_kind="fed1",init_is_success="false",otel_scope_name="apollo/router"} 1"#, - None, - ) - .await; - router.execute_default_query().await; - router.graceful_shutdown().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn fed1_schema_with_legacy_qp_reload_to_both_best_effort_keep_previous_config() { - let config = format!("{PROMETHEUS_METRICS_CONFIG}\n{LEGACY_QP}"); - let mut router = IntegrationTest::builder() - .config(config) - .supergraph("../examples/graphql/supergraph-fed1.graphql") - .build() - .await; - router.start().await; - router.assert_started().await; - router.execute_default_query().await; - - let config = format!("{PROMETHEUS_METRICS_CONFIG}\n{BOTH_BEST_EFFORT_QP}"); - router.update_config(&config).await; - router - .assert_log_contains( - "Falling back to the legacy query planner: \ - failed to initialize the query planner: \ - Supergraphs composed with federation version 1 are not supported. \ - Please recompose your supergraph with federation version 2 or greater", - ) - .await; - router - .assert_metrics_contains( - r#"apollo_router_lifecycle_query_planner_init_total{init_error_kind="fed1",init_is_success="false",otel_scope_name="apollo/router"} 1"#, - None, - ) - .await; - router.execute_default_query().await; - router.graceful_shutdown().await; -} - #[tokio::test(flavor = "multi_thread")] async fn fed2_schema_with_new_qp() { - let config = format!("{PROMETHEUS_METRICS_CONFIG}\n{NEW_QP}"); let mut router = IntegrationTest::builder() - .config(config) + .config(PROMETHEUS_METRICS_CONFIG) .supergraph("../examples/graphql/supergraph.graphql") .build() .await; @@ -182,29 +44,13 @@ async fn fed2_schema_with_new_qp() { router.graceful_shutdown().await; } -#[tokio::test(flavor = "multi_thread")] -async fn context_with_legacy_qp() { - if !graph_os_enabled() { - return; - } - let mut router = IntegrationTest::builder() - .config(PROMETHEUS_METRICS_CONFIG) - .supergraph("tests/fixtures/set_context/supergraph.graphql") - .build() - .await; - router.start().await; - router.assert_started().await; - router.execute_default_query().await; - router.graceful_shutdown().await; -} - #[tokio::test(flavor = "multi_thread")] async fn context_with_new_qp() { if !graph_os_enabled() { return; } let mut router = IntegrationTest::builder() - .config(NEW_QP) + .config("{}") // Default config .supergraph("tests/fixtures/set_context/supergraph.graphql") .build() .await; @@ -214,61 +60,10 @@ async fn context_with_new_qp() { router.graceful_shutdown().await; } -#[tokio::test(flavor = "multi_thread")] -async fn invalid_schema_with_legacy_qp_fails_startup() { - let mut router = IntegrationTest::builder() - .config(LEGACY_QP) - .supergraph("tests/fixtures/broken-supergraph.graphql") - .build() - .await; - router.start().await; - router - .assert_log_contains( - "could not create router: \ - Federation error: Invalid supergraph: must be a core schema", - ) - .await; - router.assert_shutdown().await; -} - #[tokio::test(flavor = "multi_thread")] async fn invalid_schema_with_new_qp_fails_startup() { let mut router = IntegrationTest::builder() - .config(NEW_QP) - .supergraph("tests/fixtures/broken-supergraph.graphql") - .build() - .await; - router.start().await; - router - .assert_log_contains( - "could not create router: \ - Federation error: Invalid supergraph: must be a core schema", - ) - .await; - router.assert_shutdown().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn invalid_schema_with_both_qp_fails_startup() { - let mut router = IntegrationTest::builder() - .config(BOTH_QP) - .supergraph("tests/fixtures/broken-supergraph.graphql") - .build() - .await; - router.start().await; - router - .assert_log_contains( - "could not create router: \ - Federation error: Invalid supergraph: must be a core schema", - ) - .await; - router.assert_shutdown().await; -} - -#[tokio::test(flavor = "multi_thread")] -async fn invalid_schema_with_both_best_effort_qp_fails_startup() { - let mut router = IntegrationTest::builder() - .config(BOTH_BEST_EFFORT_QP) + .config("{}") // Default config .supergraph("tests/fixtures/broken-supergraph.graphql") .build() .await; @@ -284,9 +79,8 @@ async fn invalid_schema_with_both_best_effort_qp_fails_startup() { #[tokio::test(flavor = "multi_thread")] async fn valid_schema_with_new_qp_change_to_broken_schema_keeps_old_config() { - let config = format!("{PROMETHEUS_METRICS_CONFIG}\n{NEW_QP}"); let mut router = IntegrationTest::builder() - .config(config) + .config(PROMETHEUS_METRICS_CONFIG) .supergraph("tests/fixtures/valid-supergraph.graphql") .build() .await; diff --git a/apollo-router/tests/integration/query_planner/max_evaluated_plans.rs b/apollo-router/tests/integration/query_planner/max_evaluated_plans.rs index d6474aa30b..4e55f37757 100644 --- a/apollo-router/tests/integration/query_planner/max_evaluated_plans.rs +++ b/apollo-router/tests/integration/query_planner/max_evaluated_plans.rs @@ -49,52 +49,11 @@ async fn reports_evaluated_plans() { router.graceful_shutdown().await; } -#[tokio::test(flavor = "multi_thread")] -async fn does_not_exceed_max_evaluated_plans_legacy() { - let mut router = IntegrationTest::builder() - .config( - r#" - experimental_query_planner_mode: legacy - telemetry: - exporters: - metrics: - prometheus: - enabled: true - supergraph: - query_planning: - experimental_plans_limit: 4 - "#, - ) - .supergraph("tests/integration/fixtures/query_planner_max_evaluated_plans.graphql") - .build() - .await; - router.start().await; - router.assert_started().await; - router - .execute_query(&json!({ - "query": r#"{ t { v1 v2 v3 v4 } }"#, - "variables": {}, - })) - .await; - - let metrics = router - .get_metrics_response() - .await - .expect("failed to fetch metrics") - .text() - .await - .expect("metrics are not text?!"); - assert_evaluated_plans(&metrics, 4); - - router.graceful_shutdown().await; -} - #[tokio::test(flavor = "multi_thread")] async fn does_not_exceed_max_evaluated_plans() { let mut router = IntegrationTest::builder() .config( r#" - experimental_query_planner_mode: new telemetry: exporters: metrics: diff --git a/apollo-router/tests/integration/redis.rs b/apollo-router/tests/integration/redis.rs index f5e3da3438..b7fb1cbf58 100644 --- a/apollo-router/tests/integration/redis.rs +++ b/apollo-router/tests/integration/redis.rs @@ -1,6 +1,5 @@ // The redis cache keys in this file have to change whenever the following change: // * the supergraph schema -// * experimental_query_planner_mode // * federation version // // How to get the new cache key: @@ -52,7 +51,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:924b36a9ae6af4ff198220b1302b14b6329c4beb7c022fd31d6fef82eaad7ccb", + "plan:router:{}:8c0b4bfb4630635c2b5748c260d686ddb301d164e5818c63d6d9d77e13631676:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:d9f7a00bc249cb51cfc8599f86b6dc5272967b37b1409dc4717f105b6939fe43", env!("CARGO_PKG_VERSION") ); @@ -992,23 +991,13 @@ 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:28acec2bebc3922cd261ed3c8a13b26d53b49e891797a199e3e1ce8089e813e6", + "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:fb1a8e6e454ad6a1d0d48b24dc9c7c4dd6d9bf58b6fdaf43cd24eb77fbbb3a17", env!("CARGO_PKG_VERSION") ), ) .await; } -#[tokio::test(flavor = "multi_thread")] -#[ignore = "the cache key for different query planner modes is currently different"] -async fn query_planner_redis_update_planner_mode() { - test_redis_query_plan_config_update( - include_str!("fixtures/query_planner_redis_config_update_query_planner_mode.router.yaml"), - "", - ) - .await; -} - #[tokio::test(flavor = "multi_thread")] async fn query_planner_redis_update_defer() { // If this test fails and the cache key format changed you'll need to update @@ -1025,7 +1014,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:af3139ddd647c755d2eab5e6a177dc443030a528db278c19ad7b45c5c0324378", + "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:dc062fcc9cfd9582402d1e8b1fa3ee336ea1804d833443869e0b3744996716a2", env!("CARGO_PKG_VERSION") ), ) @@ -1050,33 +1039,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:8b87abe2e45d38df4712af966aa540f33dbab6fc2868a409f2dbb6a5a4fb2d08", - env!("CARGO_PKG_VERSION") - ), - ) - .await; -} - -// TODO drop this test once we remove the JS QP -#[tokio::test(flavor = "multi_thread")] -async fn query_planner_redis_update_reuse_query_fragments() { - // 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. - // - // You first need to follow the process and update the key in - // `test_redis_query_plan_config_update`, and then update the key in this - // test. - // - // This test requires graphos license, so make sure you have - // "TEST_APOLLO_KEY" and "TEST_APOLLO_GRAPH_REF" env vars set, otherwise the - // test just passes locally. - test_redis_query_plan_config_update( - include_str!( - "fixtures/query_planner_redis_config_update_reuse_query_fragments.router.yaml" - ), - &format!( - "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:9af18c8afd568c197050fc1a60c52a8c98656f1775016110516fabfbedc135fe", + "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05:opname:3973e022e93220f9212c18d0d0c543ae7c309e46640da93a4a0314de999f5112:metadata:bdc09980aa6ef28a67f5aeb8759763d8ac5a4fc43afa8c5a89f58cc998c48db3", env!("CARGO_PKG_VERSION") ), ) @@ -1104,7 +1067,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:924b36a9ae6af4ff198220b1302b14b6329c4beb7c022fd31d6fef82eaad7ccb", + "plan:router:{}:5938623f2155169070684a48be1e0b8468d0f2c662b5527a2247f683173f7d05: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/telemetry/metrics.rs b/apollo-router/tests/integration/telemetry/metrics.rs index cbce509c13..cd9c1c4550 100644 --- a/apollo-router/tests/integration/telemetry/metrics.rs +++ b/apollo-router/tests/integration/telemetry/metrics.rs @@ -273,24 +273,6 @@ async fn test_gauges_on_reload() { router .assert_metrics_contains(r#"apollo_router_cache_storage_estimated_size{kind="query planner",type="memory",otel_scope_name="apollo/router"} "#, None) .await; - router - .assert_metrics_contains( - r#"apollo_router_query_planning_queued{otel_scope_name="apollo/router"} "#, - None, - ) - .await; - router - .assert_metrics_contains( - r#"apollo_router_v8_heap_total_bytes{otel_scope_name="apollo/router"} "#, - None, - ) - .await; - router - .assert_metrics_contains( - r#"apollo_router_v8_heap_total_bytes{otel_scope_name="apollo/router"} "#, - None, - ) - .await; router .assert_metrics_contains( r#"apollo_router_cache_size{kind="APQ",type="memory",otel_scope_name="apollo/router"} 1"#, diff --git a/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration.yaml b/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration.yaml index 321fd80abd..3f64e39a39 100644 --- a/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration.yaml +++ b/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration.yaml @@ -7,7 +7,5 @@ telemetry: stdout: format: text -experimental_query_planner_mode: legacy - plugins: experimental.expose_query_plan: true \ No newline at end of file diff --git a/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration2.yaml b/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration2.yaml index 7c445ce5b2..44ce15d711 100644 --- a/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration2.yaml +++ b/apollo-router/tests/samples/enterprise/progressive-override/basic/configuration2.yaml @@ -7,8 +7,6 @@ telemetry: stdout: format: text -experimental_query_planner_mode: legacy - rhai: scripts: "tests/samples/enterprise/progressive-override/basic/rhai" main: "main.rhai" diff --git a/apollo-router/tests/samples/enterprise/progressive-override/basic/plan.json b/apollo-router/tests/samples/enterprise/progressive-override/basic/plan.json index 6cdf9d7b60..6bcbdecf5a 100644 --- a/apollo-router/tests/samples/enterprise/progressive-override/basic/plan.json +++ b/apollo-router/tests/samples/enterprise/progressive-override/basic/plan.json @@ -11,7 +11,7 @@ { "request": { "body": { - "query": "query progressive1__Subgraph1__0{percent100{__typename id}}", + "query": "query progressive1__Subgraph1__0 { percent100 { __typename id } }", "operationName": "progressive1__Subgraph1__0" } }, @@ -33,7 +33,7 @@ { "request": { "body": { - "query": "query progressive1__Subgraph2__1($representations:[_Any!]!){_entities(representations:$representations){...on T{foo}}}", + "query": "query progressive1__Subgraph2__1($representations: [_Any!]!) { _entities(representations: $representations) { ... on T { foo } } }", "operationName": "progressive1__Subgraph2__1", "variables": { "representations": [ @@ -60,7 +60,7 @@ { "request": { "body": { - "query": "query progressive2__Subgraph2__0{percent0{foo}}", + "query": "query progressive2__Subgraph2__0 { percent0 { foo } }", "operationName": "progressive2__Subgraph2__0" } }, @@ -119,7 +119,7 @@ { "request": { "body": { - "query": "query progressive3__Subgraph1__0{percent100{__typename id}}", + "query": "query progressive3__Subgraph1__0 { percent100 { __typename id } }", "operationName": "progressive3__Subgraph1__0" } }, @@ -137,7 +137,7 @@ { "request": { "body": { - "query": "query progressive4__Subgraph1__0{percent100{bar}}", + "query": "query progressive4__Subgraph1__0 { percent100 { bar } }", "operationName": "progressive4__Subgraph1__0" } }, @@ -158,7 +158,7 @@ { "request": { "body": { - "query": "query progressive3__Subgraph2__1($representations:[_Any!]!){_entities(representations:$representations){...on T{bar}}}", + "query": "query progressive3__Subgraph2__1($representations: [_Any!]!) { _entities(representations: $representations) { ... on T { bar } } }", "operationName": "progressive3__Subgraph2__1", "variables": { "representations": [ diff --git a/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration.yaml b/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration.yaml index b069d5af18..8d54c2ee26 100644 --- a/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration.yaml +++ b/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration.yaml @@ -11,7 +11,5 @@ telemetry: stdout: format: text -experimental_query_planner_mode: legacy - plugins: experimental.expose_query_plan: true \ No newline at end of file diff --git a/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration2.yaml b/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration2.yaml index 413d26aba3..fa34365889 100644 --- a/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration2.yaml +++ b/apollo-router/tests/samples/enterprise/progressive-override/warmup/configuration2.yaml @@ -11,8 +11,6 @@ telemetry: stdout: format: text -experimental_query_planner_mode: legacy - # rhai: # scripts: "tests/samples/enterprise/progressive-override/rhai" # main: "main.rhai" diff --git a/apollo-router/tests/samples/enterprise/progressive-override/warmup/plan.json b/apollo-router/tests/samples/enterprise/progressive-override/warmup/plan.json index 8f913eb5be..288a933d4b 100644 --- a/apollo-router/tests/samples/enterprise/progressive-override/warmup/plan.json +++ b/apollo-router/tests/samples/enterprise/progressive-override/warmup/plan.json @@ -11,7 +11,7 @@ { "request": { "body": { - "query": "query progressive1__Subgraph1__0{percent100{__typename id}}", + "query": "query progressive1__Subgraph1__0 { percent100 { __typename id } }", "operationName": "progressive1__Subgraph1__0" } }, @@ -33,7 +33,7 @@ { "request": { "body": { - "query": "query progressive1__Subgraph2__1($representations:[_Any!]!){_entities(representations:$representations){...on T{foo}}}", + "query": "query progressive1__Subgraph2__1($representations: [_Any!]!) { _entities(representations: $representations) { ... on T { foo } } }", "operationName": "progressive1__Subgraph2__1", "variables": { "representations": [ @@ -60,7 +60,7 @@ { "request": { "body": { - "query": "query progressive2__Subgraph2__0{percent0{foo}}", + "query": "query progressive2__Subgraph2__0 { percent0 { foo } }", "operationName": "progressive2__Subgraph2__0" } }, @@ -106,7 +106,7 @@ { "request": { "body": { - "query": "query progressive1__Subgraph1__0{percent100{__typename id}}", + "query": "query progressive1__Subgraph1__0 { percent100 { __typename id } }", "operationName": "progressive1__Subgraph1__0" } }, @@ -128,7 +128,7 @@ { "request": { "body": { - "query": "query progressive1__Subgraph2__1($representations:[_Any!]!){_entities(representations:$representations){...on T{foo}}}", + "query": "query progressive1__Subgraph2__1($representations: [_Any!]!) { _entities(representations: $representations) { ... on T { foo } } }", "operationName": "progressive1__Subgraph2__1", "variables": { "representations": [ @@ -155,7 +155,7 @@ { "request": { "body": { - "query": "query progressive2__Subgraph2__0{percent0{foo}}", + "query": "query progressive2__Subgraph2__0 { percent0 { foo } }", "operationName": "progressive2__Subgraph2__0" } }, diff --git a/apollo-router/tests/set_context.rs b/apollo-router/tests/set_context.rs index 96086bbe4c..6ebb9e590b 100644 --- a/apollo-router/tests/set_context.rs +++ b/apollo-router/tests/set_context.rs @@ -32,26 +32,8 @@ macro_rules! snap } } -fn get_configuration(rust_qp: bool) -> serde_json::Value { - if rust_qp { - return json! {{ - "experimental_query_planner_mode": "new", - "experimental_type_conditioned_fetching": true, - // will make debugging easier - "plugins": { - "experimental.expose_query_plan": true - }, - "include_subgraph_errors": { - "all": true - }, - "supergraph": { - // TODO(@goto-bus-stop): need to update the mocks and remove this, #6013 - "generate_query_fragments": false, - } - }}; - } +fn get_configuration() -> serde_json::Value { json! {{ - "experimental_query_planner_mode": "legacy", "experimental_type_conditioned_fetching": true, // will make debugging easier "plugins": { @@ -67,12 +49,8 @@ fn get_configuration(rust_qp: bool) -> serde_json::Value { }} } -async fn run_single_request( - query: &str, - rust_qp: bool, - mocks: &[(&'static str, &'static str)], -) -> Response { - let configuration = get_configuration(rust_qp); +async fn run_single_request(query: &str, mocks: &[(&'static str, &'static str)]) -> Response { + let configuration = get_configuration(); let harness = setup_from_mocks(configuration, mocks); let supergraph_service = harness.build_supergraph().await.unwrap(); let request = supergraph::Request::fake_builder() @@ -91,33 +69,6 @@ async fn run_single_request( .unwrap() } -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context() { - static QUERY: &str = r#" - query Query { - t { - __typename - id - u { - __typename - field - } - } - }"#; - - let response = run_single_request( - QUERY, - false, - &[ - ("Subgraph1", include_str!("fixtures/set_context/one.json")), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - #[tokio::test(flavor = "multi_thread")] async fn test_set_context_rust_qp() { static QUERY: &str = r#" @@ -134,32 +85,6 @@ async fn test_set_context_rust_qp() { let response = run_single_request( QUERY, - true, - &[ - ("Subgraph1", include_str!("fixtures/set_context/one.json")), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_no_typenames() { - static QUERY_NO_TYPENAMES: &str = r#" - query Query { - t { - id - u { - field - } - } - }"#; - - let response = run_single_request( - QUERY_NO_TYPENAMES, - false, &[ ("Subgraph1", include_str!("fixtures/set_context/one.json")), ("Subgraph2", include_str!("fixtures/set_context/two.json")), @@ -184,32 +109,6 @@ async fn test_set_context_no_typenames_rust_qp() { let response = run_single_request( QUERY_NO_TYPENAMES, - true, - &[ - ("Subgraph1", include_str!("fixtures/set_context/one.json")), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_list() { - static QUERY_WITH_LIST: &str = r#" - query Query { - t { - id - uList { - field - } - } - }"#; - - let response = run_single_request( - QUERY_WITH_LIST, - false, &[ ("Subgraph1", include_str!("fixtures/set_context/one.json")), ("Subgraph2", include_str!("fixtures/set_context/two.json")), @@ -234,32 +133,6 @@ async fn test_set_context_list_rust_qp() { let response = run_single_request( QUERY_WITH_LIST, - true, - &[ - ("Subgraph1", include_str!("fixtures/set_context/one.json")), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_list_of_lists() { - static QUERY_WITH_LIST_OF_LISTS: &str = r#" - query QueryLL { - tList { - id - uList { - field - } - } - }"#; - - let response = run_single_request( - QUERY_WITH_LIST_OF_LISTS, - false, &[ ("Subgraph1", include_str!("fixtures/set_context/one.json")), ("Subgraph2", include_str!("fixtures/set_context/two.json")), @@ -284,38 +157,6 @@ async fn test_set_context_list_of_lists_rust_qp() { let response = run_single_request( QUERY_WITH_LIST_OF_LISTS, - true, - &[ - ("Subgraph1", include_str!("fixtures/set_context/one.json")), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_union() { - static QUERY_WITH_UNION: &str = r#" - query QueryUnion { - k { - ... on A { - v { - field - } - } - ... on B { - v { - field - } - } - } - }"#; - - let response = run_single_request( - QUERY_WITH_UNION, - false, &[ ("Subgraph1", include_str!("fixtures/set_context/one.json")), ("Subgraph2", include_str!("fixtures/set_context/two.json")), @@ -346,7 +187,6 @@ async fn test_set_context_union_rust_qp() { let response = run_single_request( QUERY_WITH_UNION, - true, &[ ("Subgraph1", include_str!("fixtures/set_context/one.json")), ("Subgraph2", include_str!("fixtures/set_context/two.json")), @@ -357,34 +197,6 @@ async fn test_set_context_union_rust_qp() { snap!(response); } -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_with_null() { - static QUERY: &str = r#" - query Query_Null_Param { - t { - id - u { - field - } - } - }"#; - - let response = run_single_request( - QUERY, - false, - &[ - ( - "Subgraph1", - include_str!("fixtures/set_context/one_null_param.json"), - ), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - insta::assert_json_snapshot!(response); -} - #[tokio::test(flavor = "multi_thread")] async fn test_set_context_with_null_rust_qp() { static QUERY: &str = r#" @@ -399,7 +211,6 @@ async fn test_set_context_with_null_rust_qp() { let response = run_single_request( QUERY, - true, &[ ( "Subgraph1", @@ -413,33 +224,6 @@ async fn test_set_context_with_null_rust_qp() { insta::assert_json_snapshot!(response); } -// this test returns the contextual value with a different than expected type -// this currently works, but perhaps should do type valdiation in the future to reject -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_type_mismatch() { - static QUERY: &str = r#" - query Query_type_mismatch { - t { - id - u { - field - } - } - }"#; - - let response = run_single_request( - QUERY, - false, - &[ - ("Subgraph1", include_str!("fixtures/set_context/one.json")), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - // this test returns the contextual value with a different than expected type // this currently works, but perhaps should do type valdiation in the future to reject #[tokio::test(flavor = "multi_thread")] @@ -456,7 +240,6 @@ async fn test_set_context_type_mismatch_rust_qp() { let response = run_single_request( QUERY, - true, &[ ("Subgraph1", include_str!("fixtures/set_context/one.json")), ("Subgraph2", include_str!("fixtures/set_context/two.json")), @@ -467,37 +250,6 @@ async fn test_set_context_type_mismatch_rust_qp() { snap!(response); } -// fetch from unrelated (to context) subgraph fails -// validates that the error propagation is correct -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_unrelated_fetch_failure() { - static QUERY: &str = r#" - query Query_fetch_failure { - t { - id - u { - field - b - } - } - }"#; - - let response = run_single_request( - QUERY, - false, - &[ - ( - "Subgraph1", - include_str!("fixtures/set_context/one_fetch_failure.json"), - ), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - // fetch from unrelated (to context) subgraph fails // validates that the error propagation is correct #[tokio::test(flavor = "multi_thread")] @@ -515,7 +267,6 @@ async fn test_set_context_unrelated_fetch_failure_rust_qp() { let response = run_single_request( QUERY, - true, &[ ( "Subgraph1", @@ -529,36 +280,6 @@ async fn test_set_context_unrelated_fetch_failure_rust_qp() { snap!(response); } -// subgraph fetch fails where context depends on results of fetch. -// validates that no fetch will get called that passes context -#[tokio::test(flavor = "multi_thread")] -async fn test_set_context_dependent_fetch_failure() { - static QUERY: &str = r#" - query Query_fetch_dependent_failure { - t { - id - u { - field - } - } - }"#; - - let response = run_single_request( - QUERY, - false, - &[ - ( - "Subgraph1", - include_str!("fixtures/set_context/one_dependent_fetch_failure.json"), - ), - ("Subgraph2", include_str!("fixtures/set_context/two.json")), - ], - ) - .await; - - snap!(response); -} - // subgraph fetch fails where context depends on results of fetch. // validates that no fetch will get called that passes context #[tokio::test(flavor = "multi_thread")] @@ -575,7 +296,6 @@ async fn test_set_context_dependent_fetch_failure_rust_qp() { let response = run_single_request( QUERY, - true, &[ ( "Subgraph1", 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 de7f4d2827..4d61ae95b4 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 @@ -79,7 +79,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "18631e67bb0c6b514cb51e8dff155a2900c8000ad319ea4784e5ca8b1275aca2", + "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", "authorization": { "is_authenticated": false, "scopes": [], @@ -137,7 +137,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "7f3ec4c2c644d43e54d95da83790166d87ab6bfcb31fe5692d8262199bff6d3f", + "schemaAwareHash": "66a2bd39c499f1edd8c3ec1bfbc170cb995c6f9e23427b5486b633decd2da08b", "authorization": { "is_authenticated": false, "scopes": [], @@ -148,7 +148,7 @@ expression: response ] } }, - "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n \n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n \n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n \n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Flatten(path: \"search.@.sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n },\n}" + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n\n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n\n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n\n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Flatten(path: \"search.@.sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n },\n}" } } } 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 39b73eabbc..c89cab9c7d 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 @@ -79,7 +79,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "18631e67bb0c6b514cb51e8dff155a2900c8000ad319ea4784e5ca8b1275aca2", + "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", "authorization": { "is_authenticated": false, "scopes": [], @@ -92,9 +92,8 @@ expression: response { "kind": "Flatten", "path": [ - "", "search", - "@|[MovieResult]", + "@|[ArticleResult]", "sections", "@" ], @@ -104,7 +103,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -118,7 +117,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -132,16 +131,16 @@ expression: response } ], "variableUsages": [ - "movieResultParam" + "articleResultParam" ], - "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", + "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", "operationName": "Search__artworkSubgraph__1", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "7f3ec4c2c644d43e54d95da83790166d87ab6bfcb31fe5692d8262199bff6d3f", + "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", "authorization": { "is_authenticated": false, "scopes": [], @@ -152,9 +151,8 @@ expression: response { "kind": "Flatten", "path": [ - "", "search", - "@|[ArticleResult]", + "@|[MovieResult]", "sections", "@" ], @@ -164,7 +162,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -178,7 +176,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -192,16 +190,16 @@ expression: response } ], "variableUsages": [ - "articleResultParam" + "movieResultParam" ], - "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", + "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", "operationName": "Search__artworkSubgraph__2", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "3874fd9db4a0302422701b93506f42a5de5604355be7093fa2abe23f440161f9", + "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", "authorization": { "is_authenticated": false, "scopes": [], @@ -214,7 +212,7 @@ expression: response ] } }, - "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n \n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n \n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n \n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \".search.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n Flatten(path: \".search.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n },\n },\n}" + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n\n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n\n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n\n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \"search.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n Flatten(path: \"search.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n },\n },\n}" } } } 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 39b73eabbc..c89cab9c7d 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 @@ -79,7 +79,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "18631e67bb0c6b514cb51e8dff155a2900c8000ad319ea4784e5ca8b1275aca2", + "schemaAwareHash": "1d2d8b1ab80b4b1293e9753a915997835e6ff5bc54ba4c9b400abe7fa4661386", "authorization": { "is_authenticated": false, "scopes": [], @@ -92,9 +92,8 @@ expression: response { "kind": "Flatten", "path": [ - "", "search", - "@|[MovieResult]", + "@|[ArticleResult]", "sections", "@" ], @@ -104,7 +103,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -118,7 +117,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -132,16 +131,16 @@ expression: response } ], "variableUsages": [ - "movieResultParam" + "articleResultParam" ], - "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", + "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", "operationName": "Search__artworkSubgraph__1", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "7f3ec4c2c644d43e54d95da83790166d87ab6bfcb31fe5692d8262199bff6d3f", + "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", "authorization": { "is_authenticated": false, "scopes": [], @@ -152,9 +151,8 @@ expression: response { "kind": "Flatten", "path": [ - "", "search", - "@|[ArticleResult]", + "@|[MovieResult]", "sections", "@" ], @@ -164,7 +162,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -178,7 +176,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -192,16 +190,16 @@ expression: response } ], "variableUsages": [ - "articleResultParam" + "movieResultParam" ], - "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", + "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", "operationName": "Search__artworkSubgraph__2", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "3874fd9db4a0302422701b93506f42a5de5604355be7093fa2abe23f440161f9", + "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", "authorization": { "is_authenticated": false, "scopes": [], @@ -214,7 +212,7 @@ expression: response ] } }, - "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n \n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n \n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n \n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \".search.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n Flatten(path: \".search.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n },\n },\n}" + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n\n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n\n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n\n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \"search.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n Flatten(path: \"search.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n },\n },\n}" } } } 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 ec86110080..371fd3496e 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 @@ -141,7 +141,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "65c1648beef44b81ac988224191b18ff469c641fd33032ef0c84165245018b62", + "schemaAwareHash": "b74616ae898acf3abefb83e24bde5faf0de0f9475d703b105b60c18c7372ab13", "authorization": { "is_authenticated": false, "scopes": [], @@ -154,10 +154,9 @@ expression: response { "kind": "Flatten", "path": [ - "", "searchListOfList", "@", - "@|[MovieResult]", + "@|[ArticleResult]", "sections", "@" ], @@ -167,7 +166,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -181,7 +180,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -195,16 +194,16 @@ expression: response } ], "variableUsages": [ - "movieResultParam" + "articleResultParam" ], - "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", + "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", "operationName": "Search__artworkSubgraph__1", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "7f3ec4c2c644d43e54d95da83790166d87ab6bfcb31fe5692d8262199bff6d3f", + "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", "authorization": { "is_authenticated": false, "scopes": [], @@ -215,10 +214,9 @@ expression: response { "kind": "Flatten", "path": [ - "", "searchListOfList", "@", - "@|[ArticleResult]", + "@|[MovieResult]", "sections", "@" ], @@ -228,7 +226,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -242,7 +240,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -256,16 +254,16 @@ expression: response } ], "variableUsages": [ - "articleResultParam" + "movieResultParam" ], - "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", + "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", "operationName": "Search__artworkSubgraph__2", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "3874fd9db4a0302422701b93506f42a5de5604355be7093fa2abe23f440161f9", + "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", "authorization": { "is_authenticated": false, "scopes": [], @@ -278,7 +276,7 @@ expression: response ] } }, - "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n searchListOfList {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n \n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n \n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n \n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \".searchListOfList.@.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n Flatten(path: \".searchListOfList.@.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n },\n },\n}" + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n searchListOfList {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n\n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n\n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n\n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \"searchListOfList.@.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n Flatten(path: \"searchListOfList.@.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n },\n },\n}" } } } 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 e4ba5927a3..354bd034a9 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 @@ -145,7 +145,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "f2466229a91f69cadfa844a20343b03668b7f85fd1310a4b20ba9382ffa2f5e7", + "schemaAwareHash": "dc1df8e8d701876c6ea7d25bbeab92a5629a82e55660ccc48fc37e12d5157efa", "authorization": { "is_authenticated": false, "scopes": [], @@ -158,11 +158,10 @@ expression: response { "kind": "Flatten", "path": [ - "", "searchListOfListOfList", "@", "@", - "@|[MovieResult]", + "@|[ArticleResult]", "sections", "@" ], @@ -172,7 +171,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -186,7 +185,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -200,16 +199,16 @@ expression: response } ], "variableUsages": [ - "movieResultParam" + "articleResultParam" ], - "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", + "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", "operationName": "Search__artworkSubgraph__1", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "7f3ec4c2c644d43e54d95da83790166d87ab6bfcb31fe5692d8262199bff6d3f", + "schemaAwareHash": "31465e7b7e358ea9407067188249b51fd7342088e6084360ed0df28199cef5cc", "authorization": { "is_authenticated": false, "scopes": [], @@ -220,11 +219,10 @@ expression: response { "kind": "Flatten", "path": [ - "", "searchListOfListOfList", "@", "@", - "@|[ArticleResult]", + "@|[MovieResult]", "sections", "@" ], @@ -234,7 +232,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -248,7 +246,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -262,16 +260,16 @@ expression: response } ], "variableUsages": [ - "articleResultParam" + "movieResultParam" ], - "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ..._generated_onEntityCollectionSection2_0 } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { artwork(params: $articleResultParam) title }", + "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ..._generated_onEntityCollectionSection2_0 ... on GallerySection { artwork(params: $movieResultParam) } } } fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection { title artwork(params: $movieResultParam) }", "operationName": "Search__artworkSubgraph__2", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "3874fd9db4a0302422701b93506f42a5de5604355be7093fa2abe23f440161f9", + "schemaAwareHash": "550ad525da9bb9497fb0d51bf7a64b7d5d73ade5ee7d2e425573dc7e2e248e99", "authorization": { "is_authenticated": false, "scopes": [], @@ -284,7 +282,7 @@ expression: response ] } }, - "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n searchListOfListOfList {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n \n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n \n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n \n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \".searchListOfListOfList.@.@.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n Flatten(path: \".searchListOfListOfList.@.@.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n \n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n },\n },\n}" + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n searchListOfListOfList {\n __typename\n ..._generated_onMovieResult2_0\n ..._generated_onArticleResult2_0\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n __typename\n id\n }\n\n fragment _generated_onGallerySection2_0 on GallerySection {\n __typename\n id\n }\n\n fragment _generated_onMovieResult2_0 on MovieResult {\n sections {\n __typename\n ..._generated_onEntityCollectionSection2_0\n ..._generated_onGallerySection2_0\n }\n id\n }\n\n fragment _generated_onArticleResult2_0 on ArticleResult {\n id\n sections {\n __typename\n ..._generated_onGallerySection2_0\n ..._generated_onEntityCollectionSection2_0\n }\n }\n },\n Parallel {\n Flatten(path: \"searchListOfListOfList.@.@.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ..._generated_onEntityCollectionSection2_0\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n },\n },\n Flatten(path: \"searchListOfListOfList.@.@.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ..._generated_onEntityCollectionSection2_0\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n\n fragment _generated_onEntityCollectionSection2_0 on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n },\n },\n },\n },\n}" } } } 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 8ae0b59f67..8811454f74 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 @@ -54,7 +54,7 @@ expression: response "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "cc52bb826d3c06b3ccbc421340fe3f49a81dc2b71dcb6a931a9a769745038e3f", + "schemaAwareHash": "446c1a72168f736a89e4f56799333e05b26092d36fc55e22c2e92828061c787b", "authorization": { "is_authenticated": false, "scopes": [], @@ -67,9 +67,8 @@ expression: response { "kind": "Flatten", "path": [ - "", "search", - "@|[MovieResult]", + "@|[ArticleResult]", "sections", "@" ], @@ -79,7 +78,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -93,7 +92,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -107,16 +106,16 @@ expression: response } ], "variableUsages": [ - "movieResultParam" + "articleResultParam" ], - "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ... on EntityCollectionSection { title artwork(params: $movieResultParam) } ... on GallerySection { artwork(params: $movieResultParam) } } }", + "operation": "query Search__artworkSubgraph__1($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ... on EntityCollectionSection { artwork(params: $articleResultParam) title } } }", "operationName": "Search__artworkSubgraph__1", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "6e83e0a67b509381f1a0c2dfe84db92d0dd6bf4bb23fe4c97ccd3d871364c9f4", + "schemaAwareHash": "f9052a9ce97a084006a1f2054b7e0fba8734f24bb53cf0f7e0ba573c7e709b98", "authorization": { "is_authenticated": false, "scopes": [], @@ -127,9 +126,8 @@ expression: response { "kind": "Flatten", "path": [ - "", "search", - "@|[ArticleResult]", + "@|[MovieResult]", "sections", "@" ], @@ -139,7 +137,7 @@ expression: response "requires": [ { "kind": "InlineFragment", - "typeCondition": "GallerySection", + "typeCondition": "EntityCollectionSection", "selections": [ { "kind": "Field", @@ -153,7 +151,7 @@ expression: response }, { "kind": "InlineFragment", - "typeCondition": "EntityCollectionSection", + "typeCondition": "GallerySection", "selections": [ { "kind": "Field", @@ -167,16 +165,16 @@ expression: response } ], "variableUsages": [ - "articleResultParam" + "movieResultParam" ], - "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $articleResultParam: String) { _entities(representations: $representations) { ... on GallerySection { artwork(params: $articleResultParam) } ... on EntityCollectionSection { artwork(params: $articleResultParam) title } } }", + "operation": "query Search__artworkSubgraph__2($representations: [_Any!]!, $movieResultParam: String) { _entities(representations: $representations) { ... on EntityCollectionSection { title artwork(params: $movieResultParam) } ... on GallerySection { artwork(params: $movieResultParam) } } }", "operationName": "Search__artworkSubgraph__2", "operationKind": "query", "id": null, "inputRewrites": null, "outputRewrites": null, "contextRewrites": null, - "schemaAwareHash": "67834874c123139d942b140fb9ff00ed4e22df25228c3e758eeb44b28d3847eb", + "schemaAwareHash": "027cac0584184439636aea68757da18f3e0e18142948e3b8625724f93e8720fc", "authorization": { "is_authenticated": false, "scopes": [], @@ -189,7 +187,7 @@ expression: response ] } }, - "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ... on MovieResult {\n sections {\n __typename\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n }\n id\n }\n ... on ArticleResult {\n id\n sections {\n __typename\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n }\n }\n }\n }\n },\n Parallel {\n Flatten(path: \".search.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ... on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n },\n },\n Flatten(path: \".search.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ... on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n }\n },\n },\n },\n },\n}" + "text": "QueryPlan {\n Sequence {\n Fetch(service: \"searchSubgraph\") {\n {\n search {\n __typename\n ... on MovieResult {\n sections {\n __typename\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n }\n id\n }\n ... on ArticleResult {\n id\n sections {\n __typename\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n }\n }\n }\n }\n },\n Parallel {\n Flatten(path: \"search.@|[ArticleResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on GallerySection {\n __typename\n id\n }\n ... on EntityCollectionSection {\n __typename\n id\n }\n } =>\n {\n ... on GallerySection {\n artwork(params: $articleResultParam)\n }\n ... on EntityCollectionSection {\n artwork(params: $articleResultParam)\n title\n }\n }\n },\n },\n Flatten(path: \"search.@|[MovieResult].sections.@\") {\n Fetch(service: \"artworkSubgraph\") {\n {\n ... on EntityCollectionSection {\n __typename\n id\n }\n ... on GallerySection {\n __typename\n id\n }\n } =>\n {\n ... on EntityCollectionSection {\n title\n artwork(params: $movieResultParam)\n }\n ... on GallerySection {\n artwork(params: $movieResultParam)\n }\n }\n },\n },\n },\n },\n}" } } } diff --git a/apollo-router/tests/type_conditions.rs b/apollo-router/tests/type_conditions.rs index ab43b3f521..09466bee5d 100644 --- a/apollo-router/tests/type_conditions.rs +++ b/apollo-router/tests/type_conditions.rs @@ -30,45 +30,38 @@ struct RequestAndResponse { #[tokio::test(flavor = "multi_thread")] async fn test_type_conditions_enabled() { - _test_type_conditions_enabled("legacy").await; - _test_type_conditions_enabled("new").await; + _test_type_conditions_enabled().await; } #[tokio::test(flavor = "multi_thread")] async fn test_type_conditions_enabled_generate_query_fragments() { - _test_type_conditions_enabled_generate_query_fragments("legacy").await; - _test_type_conditions_enabled_generate_query_fragments("new").await; + _test_type_conditions_enabled_generate_query_fragments().await; } #[tokio::test(flavor = "multi_thread")] async fn test_type_conditions_enabled_list_of_list() { - _test_type_conditions_enabled_list_of_list("legacy").await; - _test_type_conditions_enabled_list_of_list("new").await; + _test_type_conditions_enabled_list_of_list().await; } #[tokio::test(flavor = "multi_thread")] async fn test_type_conditions_enabled_list_of_list_of_list() { - _test_type_conditions_enabled_list_of_list_of_list("legacy").await; - _test_type_conditions_enabled_list_of_list_of_list("new").await; + _test_type_conditions_enabled_list_of_list_of_list().await; } #[tokio::test(flavor = "multi_thread")] async fn test_type_conditions_disabled() { - _test_type_conditions_disabled("legacy").await; - _test_type_conditions_disabled("new").await; + _test_type_conditions_disabled().await; } #[tokio::test(flavor = "multi_thread")] async fn test_type_conditions_enabled_shouldnt_make_article_fetch() { - _test_type_conditions_enabled_shouldnt_make_article_fetch("legacy").await; - _test_type_conditions_enabled_shouldnt_make_article_fetch("new").await; + _test_type_conditions_enabled_shouldnt_make_article_fetch().await; } -async fn _test_type_conditions_enabled(planner_mode: &str) -> Response { +async fn _test_type_conditions_enabled() -> Response { let harness = setup_from_mocks( json! {{ "experimental_type_conditioned_fetching": true, - "experimental_query_planner_mode": planner_mode, // will make debugging easier "plugins": { "experimental.expose_query_plan": true @@ -113,11 +106,10 @@ async fn _test_type_conditions_enabled(planner_mode: &str) -> Response { response } -async fn _test_type_conditions_enabled_generate_query_fragments(planner_mode: &str) -> Response { +async fn _test_type_conditions_enabled_generate_query_fragments() -> Response { let harness = setup_from_mocks( json! {{ "experimental_type_conditioned_fetching": true, - "experimental_query_planner_mode": planner_mode, // will make debugging easier "plugins": { "experimental.expose_query_plan": true @@ -162,11 +154,10 @@ async fn _test_type_conditions_enabled_generate_query_fragments(planner_mode: &s response } -async fn _test_type_conditions_enabled_list_of_list(planner_mode: &str) -> Response { +async fn _test_type_conditions_enabled_list_of_list() -> Response { let harness = setup_from_mocks( json! {{ "experimental_type_conditioned_fetching": true, - "experimental_query_planner_mode": planner_mode, // will make debugging easier "plugins": { "experimental.expose_query_plan": true @@ -212,11 +203,10 @@ async fn _test_type_conditions_enabled_list_of_list(planner_mode: &str) -> Respo } // one last to make sure unnesting is correct -async fn _test_type_conditions_enabled_list_of_list_of_list(planner_mode: &str) -> Response { +async fn _test_type_conditions_enabled_list_of_list_of_list() -> Response { let harness = setup_from_mocks( json! {{ "experimental_type_conditioned_fetching": true, - "experimental_query_planner_mode": planner_mode, // will make debugging easier "plugins": { "experimental.expose_query_plan": true @@ -261,11 +251,10 @@ async fn _test_type_conditions_enabled_list_of_list_of_list(planner_mode: &str) response } -async fn _test_type_conditions_disabled(planner_mode: &str) -> Response { +async fn _test_type_conditions_disabled() -> Response { let harness = setup_from_mocks( json! {{ "experimental_type_conditioned_fetching": false, - "experimental_query_planner_mode": planner_mode, // will make debugging easier "plugins": { "experimental.expose_query_plan": true @@ -309,11 +298,10 @@ async fn _test_type_conditions_disabled(planner_mode: &str) -> Response { response } -async fn _test_type_conditions_enabled_shouldnt_make_article_fetch(planner_mode: &str) -> Response { +async fn _test_type_conditions_enabled_shouldnt_make_article_fetch() -> Response { let harness = setup_from_mocks( json! {{ "experimental_type_conditioned_fetching": true, - "experimental_query_planner_mode": planner_mode, // will make debugging easier "plugins": { "experimental.expose_query_plan": true diff --git a/docs/source/reference/router/configuration.mdx b/docs/source/reference/router/configuration.mdx index 2de7a04ecb..4b6747f7af 100644 --- a/docs/source/reference/router/configuration.mdx +++ b/docs/source/reference/router/configuration.mdx @@ -579,64 +579,6 @@ You can configure certain caching behaviors for generated query plans and APQ (b - You can configure a Redis-backed _distributed_ cache that enables multiple router instances to share cached values. For details, see [Distributed caching in the GraphOS Router](/router/configuration/distributed-caching/). - You can configure a Redis-backed _entity_ cache that enables a client query to retrieve cached entity data split between subgraph reponses. For details, see [Subgraph entity caching in the GraphOS Router](/router/configuration/entity-caching/). - - -### Native query planner - - - -Starting with v1.49.0, the router can run a Rust-native query planner. This native query planner can be run by itself to plan all queries, replacing the legacy JavaScript implementation. - - - -Starting with v1.57.0, to run the most performant and resource-efficient native query planner and to disable the V8 JavaScript runtime in the router, set the following options in your `router.yaml`: - -```yaml title="router.yaml" -experimental_query_planner_mode: new -``` - -You can also improve throughput by reducing the size of queries sent to subgraphs with the following option: - -```yaml title="router.yaml" -supergraph: - generate_query_fragments: true -``` - - - -Learn more in [Native Query Planner](/router/executing-operations/native-query-planner) docs. - - - -### Query planner pools - - - - - -You can improve the performance of the router's query planner by configuring parallelized query planning. - -By default, the query planner plans one operation at a time. It plans one operation to completion before planning the next one. This serial planning can be problematic when an operation takes a long time to plan and consequently blocks the query planner from working on other operations. - -To resolve such blocking scenarios, you can enable parallel query planning. Configure it in `router.yaml` with `supergraph.query_planning.experimental_parallelism`: - -```yaml title="router.yaml" -supergraph: - query_planning: - experimental_parallelism: auto # number of available cpus -``` - -The value of `experimental_parallelism` is the number of query planners in the router's _query planner pool_. A query planner pool is a preallocated set of query planners from which the router can use to plan operations. The total number of pools is the maximum number of query planners that can run in parallel and therefore the maximum number of operations that can be worked on simultaneously. - -Valid values of `experimental_parallelism`: -- Any integer starting from `1` -- The special value `auto`, which sets the number of query planners equal to the number of available CPUs on the router's host machine - -The default value of `experimental_parallelism` is `1`. - -In practice, you should tune `experimental_parallelism` based on metrics and benchmarks gathered from your router. - ### Enhanced operation signature normalization @@ -1273,26 +1215,6 @@ supergraph: generate_query_fragments: false ``` - - -The legacy query planner still supports an experimental algorithm that attempts to -reuse fragments from the original operation while forming subgraph requests. The -legacy query planner has to be explicitly enabled. This experimental feature used to -be enabled by default, but is still available to support subgraphs that rely on the -specific shape of fragments in an operation: - -```yaml -supergraph: - generate_query_fragments: false - experimental_reuse_query_fragments: true -``` - -Note that `generate_query_fragments` and `experimental_reuse_query_fragments` are -mutually exclusive; if both are explicitly set to `true`, `generate_query_fragments` -will take precedence. - - - ### Reusing configuration You can reuse parts of your configuration file in multiple places using standard YAML aliasing syntax: diff --git a/docs/source/routing/about-router.mdx b/docs/source/routing/about-router.mdx index 47c71d0de6..2439d70b39 100644 --- a/docs/source/routing/about-router.mdx +++ b/docs/source/routing/about-router.mdx @@ -145,5 +145,3 @@ Cloud-hosted routers automatically have access to additional GraphOS Router feat - For all available configuration options, go to [Router configuration](/graphos/reference/router/configuration) reference docs - To learn more about the intricacies of query plans, see the [example graph](/graphos/reference/federation/query-plans#example-graph) and [query plan](/graphos/reference/federation/query-plans#example-graph) in reference docs - -- For the most performant query planning, configure and use the [Rust-native query planner](/graphos/routing/query-planning/native-query-planner). diff --git a/docs/source/routing/performance/query-planner-pools.mdx b/docs/source/routing/performance/query-planner-pools.mdx deleted file mode 100644 index e278a1e030..0000000000 --- a/docs/source/routing/performance/query-planner-pools.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Query Planner Pools -subtitle: Run multiple query planners in parallel -minVersion: 1.44.0 -redirectFrom: - - /router/configuration/overview/#query-planner-pools ---- - - - -You can improve the performance of the router's query planner by configuring parallelized query planning. - -By default, the query planner plans one operation at a time. It plans one operation to completion before planning the next one. This serial planning can be problematic when an operation takes a long time to plan and consequently blocks the query planner from working on other operations. - -## Configuring query planner pools - -To resolve such blocking scenarios, you can enable parallel query planning. Configure it in `router.yaml` with `supergraph.query_planning.experimental_parallelism`: - -```yaml title="router.yaml" -supergraph: - query_planning: - experimental_parallelism: auto # number of available cpus -``` - -The value of `experimental_parallelism` is the number of query planners in the router's _query planner pool_. A query planner pool is a preallocated set of query planners from which the router can use to plan operations. The total number of pools is the maximum number of query planners that can run in parallel and therefore the maximum number of operations that can be worked on simultaneously. - -Valid values of `experimental_parallelism`: -- Any integer starting from `1` -- The special value `auto`, which sets the number of query planners equal to the number of available CPUs on the router's host machine - -The default value of `experimental_parallelism` is `1`. - -In practice, you should tune `experimental_parallelism` based on metrics and benchmarks gathered from your router. \ No newline at end of file diff --git a/docs/source/routing/query-planning/native-query-planner.mdx b/docs/source/routing/query-planning/native-query-planner.mdx deleted file mode 100644 index 7da2d5a099..0000000000 --- a/docs/source/routing/query-planning/native-query-planner.mdx +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Native Query Planner -subtitle: Run the Rust-native query planner in GraphOS Router -minVersion: 1.49.0 -redirectFrom: - - /router/configuration/experimental_query_planner_mode - - /router/executing-operations/native-query-planner ---- - - - -Learn to run the GraphOS Router with the Rust-native query planner and improve your query planning performance and scalability. - -## Background about query planner implementations - -In v1.49.0 the router introduced a [query planner](/graphos/routing/about-router#query-planning) implemented natively in Rust. This native query planner improves the overall performance and resource utilization of query planning. It exists alongside the legacy JavaScript implementation that uses the V8 JavaScript engine, and it will eventually replace the legacy implementation. - -### Comparing query planner implementations - -As part of the effort to ensure correctness and stability of the new query planner, starting in v1.53.0 the router enables both the new and legacy planners and runs them in parallel to compare their results by default. After their comparison, the router discards the native query planner's results and uses only the legacy planner to execute requests. The native query planner uses a single thread in the cold path of the router. It has a bounded queue of ten queries. If the queue is full, the router simply does not run the comparison to avoid excessive resource consumption. - -## Configuring query planning - -You can configure the `experimental_query_planner_mode` option in your `router.yaml` to set the query planner to run. - -The `experimental_query_planner_mode` option has the following supported modes: - -- `new`- enables only the new Rust-native query planner -- `legacy` - enables only the legacy JavaScript query planner -- `both_best_effort` (default) - enables both new and legacy query planners for comparison. The legacy query planner is used for execution. - - - -## Optimize native query planner - - - -To run the native query planner with the best performance and resource utilization, configure your router with the following options: - -```yaml title="router.yaml" -experimental_query_planner_mode: new -``` - - - -In router v1.56, running the native query planner with the best performance and resource utilization also requires setting `experimental_introspection_mode: new`. - - - -Setting `experimental_query_planner_mode: new` not only enables native query planning and schema introspection, it also disables the V8 JavaScript runtime used by the legacy query planner. Disabling V8 frees up CPU and memory and improves native query planning performance. - -Additionally, to enable more optimal native query planning and faster throughput by reducing the size of queries sent to subgraphs, you can enable query fragment generation with the following option: - -```yaml title="router.yaml" -supergraph: - generate_query_fragments: true -``` - - - -Regarding [fragment reuse and generation](/router/configuration/overview#fragment-reuse-and-generation), in the future the `generate_query_fragments` option will be the only option for handling fragments. - - - -## Metrics for native query planner - -When running both query planners for comparison with `experimental_query_planner_mode: both_best_effort`, the following metrics track mismatches and errors: - -- `apollo.router.operations.query_planner.both` with the following attributes: - - `generation.is_matched` (bool) - - `generation.js_error` (bool) - - `generation.rust_error` (bool) - -- `apollo.router.query_planning.plan.duration` with the following attributes to differentiate between planners: - - `planner` (rust | js) - -## Limitations of native query planner - -The native query planner doesn't implement `@context`. This is planned to be implemented in a future router release. diff --git a/fuzz/router.yaml b/fuzz/router.yaml index 6756683b4e..def8d82978 100644 --- a/fuzz/router.yaml +++ b/fuzz/router.yaml @@ -1,7 +1,6 @@ supergraph: listen: 0.0.0.0:4000 introspection: true -experimental_query_planner_mode: both sandbox: enabled: true homepage: