From 171635ceac91f79373271d563246c1fad9c50ebc Mon Sep 17 00:00:00 2001 From: Jem Gillam <6413628+jemgillam@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:27:17 +0100 Subject: [PATCH 1/7] Rename evaluating --- postgraphile/website/postgraphile/evaluating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postgraphile/website/postgraphile/evaluating.md b/postgraphile/website/postgraphile/evaluating.md index 7caedcfb6c..02c2ee6e51 100644 --- a/postgraphile/website/postgraphile/evaluating.md +++ b/postgraphile/website/postgraphile/evaluating.md @@ -1,8 +1,8 @@ --- layout: page path: /postgraphile/evaluating/ -title: Evaluating -fullTitle: Evaluating PostGraphile For Your Project +title: Adoption Analysis +fullTitle: PostGraphile Adoption Analysis --- Hopefully you’ve been convinced that PostGraphile serves an awesome GraphQL API, From 3bf5a93a051006bf9483e109f5e62f7558b9a052 Mon Sep 17 00:00:00 2001 From: Jem Gillam <6413628+jemgillam@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:35:12 +0100 Subject: [PATCH 2/7] New eval page --- postgraphile/website/postgraphile/limit-eval.md | 6 ++++++ postgraphile/website/sidebars.js | 1 + 2 files changed, 7 insertions(+) create mode 100644 postgraphile/website/postgraphile/limit-eval.md diff --git a/postgraphile/website/postgraphile/limit-eval.md b/postgraphile/website/postgraphile/limit-eval.md new file mode 100644 index 0000000000..e798f58dd1 --- /dev/null +++ b/postgraphile/website/postgraphile/limit-eval.md @@ -0,0 +1,6 @@ +--- +layout: page +path: /postgraphile/limit-eval/ +title: Limit eval() +fullTitle: Limitations of using eval() +--- diff --git a/postgraphile/website/sidebars.js b/postgraphile/website/sidebars.js index c2ba673b04..5bf01a3ec0 100644 --- a/postgraphile/website/sidebars.js +++ b/postgraphile/website/sidebars.js @@ -146,6 +146,7 @@ const sidebars = { "jwk-verification", "default-role", "testing-jest", + "limit-eval", "bundling-webpack", "multiple-schemas", "running-postgraphile-in-docker", From 8d4ab8e6e72328ac96e025b1a41a32f683c2ad08 Mon Sep 17 00:00:00 2001 From: Jem Gillam <6413628+jemgillam@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:56:48 +0100 Subject: [PATCH 3/7] Fix sidebar headings margins --- postgraphile/website/src/css/custom.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/postgraphile/website/src/css/custom.css b/postgraphile/website/src/css/custom.css index b5253e9f89..e97e46b7eb 100644 --- a/postgraphile/website/src/css/custom.css +++ b/postgraphile/website/src/css/custom.css @@ -103,6 +103,11 @@ figure figcaption { background-color: var(--docusaurus-highlighted-code-line-bg); } */ +.theme-doc-sidebar-item-link h4 { + margin-bottom: 5px; + margin-top: 5px; + padding-top: 5px; +} .ul-check li { background-image: url("../../static/img/check.svg"); background-repeat: no-repeat; From 8242e1d1105a547dd7e64e70b0e03f404c791fd4 Mon Sep 17 00:00:00 2001 From: Jem Gillam <6413628+jemgillam@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:28:34 +0100 Subject: [PATCH 4/7] Move eval page to operation --- postgraphile/website/postgraphile/{limit-eval.md => eval.md} | 4 ++-- postgraphile/website/sidebars.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename postgraphile/website/postgraphile/{limit-eval.md => eval.md} (53%) diff --git a/postgraphile/website/postgraphile/limit-eval.md b/postgraphile/website/postgraphile/eval.md similarity index 53% rename from postgraphile/website/postgraphile/limit-eval.md rename to postgraphile/website/postgraphile/eval.md index e798f58dd1..842587ac5d 100644 --- a/postgraphile/website/postgraphile/limit-eval.md +++ b/postgraphile/website/postgraphile/eval.md @@ -1,6 +1,6 @@ --- layout: page -path: /postgraphile/limit-eval/ -title: Limit eval() +path: /postgraphile/eval/ +title: Eval() fullTitle: Limitations of using eval() --- diff --git a/postgraphile/website/sidebars.js b/postgraphile/website/sidebars.js index 5bf01a3ec0..494ef202cf 100644 --- a/postgraphile/website/sidebars.js +++ b/postgraphile/website/sidebars.js @@ -92,6 +92,7 @@ const sidebars = { }, items: ["subscriptions", "live-queries"], }, + "eval", "background-tasks", "exporting-schema", "reserved-keywords", @@ -146,7 +147,6 @@ const sidebars = { "jwk-verification", "default-role", "testing-jest", - "limit-eval", "bundling-webpack", "multiple-schemas", "running-postgraphile-in-docker", From fc366b6652342bce3337538d441942a7f4a35e30 Mon Sep 17 00:00:00 2001 From: Jem Gillam <6413628+jemgillam@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:42:35 +0100 Subject: [PATCH 5/7] Draft article --- postgraphile/website/postgraphile/eval.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/postgraphile/website/postgraphile/eval.md b/postgraphile/website/postgraphile/eval.md index 842587ac5d..96df615c2a 100644 --- a/postgraphile/website/postgraphile/eval.md +++ b/postgraphile/website/postgraphile/eval.md @@ -1,6 +1,27 @@ --- layout: page path: /postgraphile/eval/ -title: Eval() +title: eval() fullTitle: Limitations of using eval() --- + +Input steps can have an `eval()` method which evaluates the real value of the step during planning. However, using `$__inputStep.eval()` during query planning can result in 2x potential plans being generated — a behavior which is likely unwanted due to the impact on planning time and only a small fraction of the generated plans actually being used. + +# Alternatives to `eval()` + +All the eval methods have a cost, but being specific about how the plans should branch reduces the number of plans generated and reduces the resulting planning time and code complexity. + +`evalHas(key)` +Results in two plans being generated, the plans branch on whether a particular `key` is set or not. + +`evalIs(val)` +Results in two plans being generated: one where `$__inputStep`'s value `=== val`, and one where it doesn't. + +`evalLength()` +Results in one plan being generated for each length of the list being used. + +:::info + +We are currently _evaulating_ whether to remove `eval()` completely from PostGraphile and Grafast in a future version (after Version 5). This is another reason why you should choose one of the alternatives above! + +::: From e6b81848b2e18b976f7242d54dff1fd409b4e18b Mon Sep 17 00:00:00 2001 From: Jem Gillam <6413628+jemgillam@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:48:42 +0100 Subject: [PATCH 6/7] Add gh issue link --- postgraphile/website/postgraphile/eval.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgraphile/website/postgraphile/eval.md b/postgraphile/website/postgraphile/eval.md index 96df615c2a..acdee6e162 100644 --- a/postgraphile/website/postgraphile/eval.md +++ b/postgraphile/website/postgraphile/eval.md @@ -22,6 +22,6 @@ Results in one plan being generated for each length of the list being used. :::info -We are currently _evaulating_ whether to remove `eval()` completely from PostGraphile and Grafast in a future version (after Version 5). This is another reason why you should choose one of the alternatives above! +We are currently _evaulating_ whether to remove `eval()` completely from PostGraphile and Grafast in a future version (after Version 5), see [issue #2060](https://github.com/graphile/crystal/issues/2060). This is another reason why you should choose one of the alternatives above! ::: From 958dbdfb7a39231a21b9431991fa5646e12e08a3 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 16 Oct 2024 12:00:56 +0100 Subject: [PATCH 7/7] Move to Grafast website and add more detail --- grafast/website/grafast/access-control.mdx | 4 + grafast/website/grafast/eval.mdx | 129 ++++++++++++++++++ grafast/website/grafast/plan-diagrams.mdx | 1 + grafast/website/grafast/polymorphism.mdx | 4 + .../grafast/production-considerations.md | 4 + postgraphile/website/postgraphile/eval.md | 27 ---- .../website/postgraphile/evaluating.md | 6 +- postgraphile/website/sidebars.js | 1 - 8 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 grafast/website/grafast/eval.mdx delete mode 100644 postgraphile/website/postgraphile/eval.md diff --git a/grafast/website/grafast/access-control.mdx b/grafast/website/grafast/access-control.mdx index 37d47f690e..4299becdf7 100644 --- a/grafast/website/grafast/access-control.mdx +++ b/grafast/website/grafast/access-control.mdx @@ -1,3 +1,7 @@ +--- +sidebar_position: 12 +--- + # Access control Access control is typically the responsibility of your business logic layer, as diff --git a/grafast/website/grafast/eval.mdx b/grafast/website/grafast/eval.mdx new file mode 100644 index 0000000000..b3f03d4bf8 --- /dev/null +++ b/grafast/website/grafast/eval.mdx @@ -0,0 +1,129 @@ +--- +title: Plan branching via `.eval*()` +sidebar_position: 13 +--- + +import Mermaid from "@theme/Mermaid"; + +Input steps (that is: steps that represent inputs to your GraphQL operations +such as `context()` and steps representing field arguments accessed through +`FieldArgs`) have a suite of `eval*` methods used to constrain query plans +based on the concrete values seen at runtime. For example, if the user has used +the `@skip` or `@include` directives to turn on/off sections of their query, +it's more efficient to have separate plans for this so that we don't overfetch +data: + +```graphql +query GetUserDetails($includeFriends: Boolean! = false) { + currentUser { + name + avatarUrl + friends(first: 100) @include(if: $includeFriends) { + name + avatarUrl + } + } +} +``` + +ᐸ2.currentUserIdᐳ"}}:::plan + Z__Value2["__Value[2∈0] ➊
ᐸcontextᐳ"]:::plan + Z__Value2 --> ZAccess6 + ZLoad7[["Load[7∈0] ➊
ᐸuserByIdᐳ"]]:::plan + ZAccess6 --> ZLoad7 + end + subgraph "includeFriends=true" + Access6{{"Access[6∈0] ➊
ᐸ2.currentUserIdᐳ"}}:::plan + __Value2["__Value[2∈0] ➊
ᐸcontextᐳ"]:::plan + __Value2 --> Access6 + Load7[["Load[7∈0] ➊
ᐸuserByIdᐳ"]]:::plan + Access6 --> Load7 + Load10[["Load[10∈0] ➊
ᐸfriendshipsByUserIdᐳ"]]:::plan + Access6 --> Load10 + __Item14[/"__Item[14∈3]
ᐸ10ᐳ"\]:::itemplan + Load10 ==> __Item14 + Access16{{"Access[16∈3]
ᐸ14.friend_idᐳ"}}:::plan + __Item14 --> Access16 + Load17[["Load[17∈3]
ᐸuserByIdᐳ"]]:::plan + Access16 --> Load17 + end + classDef bucket0 stroke:#696969 + class Bucket0,__Value2,__Value4,Access6,Load7,Load10 bucket0 + class ZAccess6,Z__Value2,ZLoad7 bucket0 + classDef bucket1 stroke:#00bfff + class Bucket1 bucket1 + classDef bucket3 stroke:#ffa500 + class Bucket3,__Item14,Access16,Load17 bucket3 + classDef bucket4 stroke:#0000ff + class Bucket4 bucket4 +`} +/> + +Grafast does this branching automatically by evaluating whether the value of +`$includeFriends` is `true` or not (`$includeFriends.evalIs(true)`) before +deciding which plan resolvers to call. This requirement is then stored as a +constraint on the plan, such that the next request may reuse the plan only if +the result of `$includeFriends.evalIs(true)` retains the same value. Thus this +allows the same operation to actually generate two plans, one for +`includeFriends=false` and one for `includeFriends=true`. + +:::note Plans are established on demand + +Grafast doesn't plan all possible values of `includeFriends` up front; instead +it just evaluates the version it's currently handling (e.g. +`includeFriends=false`) and will only plan the alternative when a request comes +through for it (e.g. with `includeFriends=true`). + +::: + +Note that this can result in up to 2x potential plans being +generated for the same operation, where `x` is the number of unique +`@skip`/`@include` variables. This reduces the "reusability" of the operation +plans - each time a new request comes through that doesn't match the +constraints of any pre-existing plans, we have to plan the entire operation +again from scratch. + +## The `.eval*()` family + +It is possible, **but recommended against**, for user plan resolvers to use +this functionality baked into Grafast. All the eval methods have a cost; being +specific about the exact circumstances under which the plan should fork reduces +the number of potential branches and reduces the resulting planning time and +code complexity. + +Note that only input-related steps implement any of these methods, and even +then these steps only implement the methods appropriate to them. Tread +carefully. + +- `.evalIs(val)` - Branches the plan into two forks: one where `$__inputStep`'s + value `=== val`, and one where it isn't. +- `.evalHas(key)` - Branches the plan into two forks based on whether the + specified `key` is set or not. +- `.evalLength()` - Branches the plan into a fork for each length of the list + seen at runtime. +- `.evalIsEmpty()` - Branches the plan into a fork based on whether the step + represents an "empty" object (an object with no attributes) or not. +- `.eval()` - Branches the plan into a fork for every possible value - **you + should almost never use `.eval()` in your plan resolvers**, instead choose one + of the more specific options above (or, better, avoid `.eval*()` altogether!) + +:::info `.eval*()` might be going away! + +We are currently _evaulating_ whether to remove `.eval*()` completely from +Grafast in a future version, see [issue +#2060](https://github.com/graphile/crystal/issues/2060). + +**If possible, you should avoid branching the plan and instead incorporate the +required logic into your step classes directly.** + +::: diff --git a/grafast/website/grafast/plan-diagrams.mdx b/grafast/website/grafast/plan-diagrams.mdx index ec6d73b01e..71b13419f2 100644 --- a/grafast/website/grafast/plan-diagrams.mdx +++ b/grafast/website/grafast/plan-diagrams.mdx @@ -1,5 +1,6 @@ --- title: "Plan diagrams" +sidebar_position: 10 --- import Mermaid from "@theme/Mermaid"; diff --git a/grafast/website/grafast/polymorphism.mdx b/grafast/website/grafast/polymorphism.mdx index 1dd79a2f93..f190fc3a4c 100644 --- a/grafast/website/grafast/polymorphism.mdx +++ b/grafast/website/grafast/polymorphism.mdx @@ -1,3 +1,7 @@ +--- +sidebar_position: 11 +--- + # Polymorphism GraphQL has two types of output polymorphism currently: interfaces and unions. diff --git a/grafast/website/grafast/production-considerations.md b/grafast/website/grafast/production-considerations.md index 14f8f66e09..e42b71e342 100644 --- a/grafast/website/grafast/production-considerations.md +++ b/grafast/website/grafast/production-considerations.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 14 +--- + # Production Considerations ## Overview diff --git a/postgraphile/website/postgraphile/eval.md b/postgraphile/website/postgraphile/eval.md deleted file mode 100644 index acdee6e162..0000000000 --- a/postgraphile/website/postgraphile/eval.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -layout: page -path: /postgraphile/eval/ -title: eval() -fullTitle: Limitations of using eval() ---- - -Input steps can have an `eval()` method which evaluates the real value of the step during planning. However, using `$__inputStep.eval()` during query planning can result in 2x potential plans being generated — a behavior which is likely unwanted due to the impact on planning time and only a small fraction of the generated plans actually being used. - -# Alternatives to `eval()` - -All the eval methods have a cost, but being specific about how the plans should branch reduces the number of plans generated and reduces the resulting planning time and code complexity. - -`evalHas(key)` -Results in two plans being generated, the plans branch on whether a particular `key` is set or not. - -`evalIs(val)` -Results in two plans being generated: one where `$__inputStep`'s value `=== val`, and one where it doesn't. - -`evalLength()` -Results in one plan being generated for each length of the list being used. - -:::info - -We are currently _evaulating_ whether to remove `eval()` completely from PostGraphile and Grafast in a future version (after Version 5), see [issue #2060](https://github.com/graphile/crystal/issues/2060). This is another reason why you should choose one of the alternatives above! - -::: diff --git a/postgraphile/website/postgraphile/evaluating.md b/postgraphile/website/postgraphile/evaluating.md index 02c2ee6e51..703182d483 100644 --- a/postgraphile/website/postgraphile/evaluating.md +++ b/postgraphile/website/postgraphile/evaluating.md @@ -1,10 +1,12 @@ --- layout: page path: /postgraphile/evaluating/ -title: Adoption Analysis -fullTitle: PostGraphile Adoption Analysis +title: Evaluating PostGraphile +fullTitle: Evaluating PostGraphile For Your Project --- +# Does PostGraphile Fit Your Project? + Hopefully you’ve been convinced that PostGraphile serves an awesome GraphQL API, but now let’s take a more critical look at whether or not you should adopt PostGraphile for your project. diff --git a/postgraphile/website/sidebars.js b/postgraphile/website/sidebars.js index 494ef202cf..c2ba673b04 100644 --- a/postgraphile/website/sidebars.js +++ b/postgraphile/website/sidebars.js @@ -92,7 +92,6 @@ const sidebars = { }, items: ["subscriptions", "live-queries"], }, - "eval", "background-tasks", "exporting-schema", "reserved-keywords",