Skip to content

Calculate the query complexity based on how nested a field is (depth limitation) #96

Open

Description

@kasir-barati

So I was trying to see how other calculate their query complexity and ended up in the hygraph website, as you can see in their doc they are saying that how they calculate the query complexity is like this:

  • Scalar fields: Each scalar field in a query contributes one point to the query complexity.
  • Relations / Unions: Relations multiply their complexity times the level of nesting in the query.

— Ref.

So I tried to find out how I can do it with this lib but it seems it is not possible with the current API. I also wanna point out that I feel like it is not completely impossible but I kinda have no clue as to how should I go about this: "multiply their complexity times the level of nesting in the query."

Any comment?

Side note: In NestJS we can see the same recommendation about scalar fields, i.e. to add one point to the overall complexity per scalar field (ref).

Activity

  1. kasir-barati commented on Dec 19, 2024

    @kasir-barati
    Author

    BTW I forked this repo since I thought I might find some kind of clue there and open a PR, but I also love to hear what you know about this.

  2. kasir-barati commented on Dec 19, 2024

    @kasir-barati
    Author

    So my first question now I know that I have access to something called node which in NestJS is part of the type defined for the complexity estimator as you can see it here

    But in runtime we have access to it:

    node

    This object is the FieldNode. Now I think I just need to look for a way to find the current fields nesting level by using node.

  3. kasir-barati commented on Dec 19, 2024

    @kasir-barati
    Author

    I had some progress, now I know I can calculate field nestedness level using node.loc.source and field.name. Although field.name is not gonna cut it since what if it is nested within nested with the same name:

    { 
      getPosts {
        id 
        author { 
          posts { 
            id 
          } 
        } 
      }
    }

    I cannot differentiate the first id from the second id but I can use field.astNode.loc.start I guess to do some string interpolation. Although I feel like I am reinventing the wheel, are not I 💦?

  4. kasir-barati commented on Dec 20, 2024

    @kasir-barati
    Author
  5. kasir-barati commented on Dec 20, 2024

    @kasir-barati
    Author

    So I managed to do it. Here is how I did it: I have a fieldDepth helper function to calculate the depth of each field. Then I mutiply it to a hard coded value for that field and add it to the childComplexity. I am not sure about the math formula since in my test app in my local system when I execute this query it takes 5 seconds to return a response.

    Of course I am not using dataloader and I have the issue of N+1. But if you're interested in it check out my code here.

    Off-topic:

    1. I've been meaning to ask about this YouTube video where Morris Matsa separates "field cost" from "type cost".
    2. He also talks about how he gives complexity of 0 to simple scalar types whereas here hygraph says that scalar fields contributes one point to the overall complexity of a query.
    3. Do you think we should add this to this lib's doc @ivome? TBH I have mixed fillings about it since this is rather implementation details and has nothing to do directly with this lib, but on the other hand I feel a lot of devs who are new to GraphQL will benefit from it since it shows how to calculate the complexity of a query.

    Any comment on these?

  6. kasir-barati commented on Dec 24, 2024

    @kasir-barati
    Author

    BTW I was digging on this topic of depth limitation and I read here that this lib does check the complexity of a query by assigning a number to each field. So when I looked at my own code I realized that most likely I do not need even that lib since I am already calculating the depth of a field and incorporate it to the field's complexity score.

    So maybe we can add it to this lib so that people know how they can limit the complexity of a query by its depth + a static score. My current implementation look like this:

    @Field(() => UserDto, {
      complexity({ childComplexity, node }: ComplexityEstimatorArgs) {
        const depth = fieldDepth(node);
        const complexity = 1 * depth + childComplexity;
    
        return complexity;
      },
    })
    author: UserDto;

    And here is the code for field-depth.ts:

    import { FieldNode } from 'graphql';
    import { fieldDepthQueryNormalizer } from './field-depth-query-normalizer.util';
    
    /**
     * @description Calculates the depth of a field
     */
    export function fieldDepth(node: Readonly<FieldNode>) {
      if (!node.loc) {
        throw 'EmptyNodeLocation';
      }
      if (!node.name.loc) {
        throw 'EmptyNodeNameLocation';
      }
    
      const normalizedSourceBody = fieldDepthQueryNormalizer(
        node.loc.source.body,
      );
    
      return (
        normalizedSourceBody.slice(0, node.name.loc.start).split('{')
          .length - 2
      );
    }

    And finally the field-depth-query-normalizer.util.ts:

    import {
      DefinitionNode,
      DocumentNode,
      Kind,
      parse,
      print,
    } from 'graphql';
    
    /**
     * @description Strips the query from fragments
     */
    export function fieldDepthQueryNormalizer(
      query: Readonly<string>,
    ): string {
      const ast = parse(query);
      const definitionNodes: DefinitionNode[] = [];
      const definitions = (ast as DocumentNode).definitions;
    
      for (const definition of definitions) {
        if (definition.kind !== Kind.OPERATION_DEFINITION) {
          continue;
        }
        definitionNodes.push(definition);
      }
    
      return definitionNodes
        .map((definitionNode) => print(definitionNode))
        .join(' ');
    }
  7. changed the title [-]Calculate the query complexity based on how nested a field is[/-] [+]Calculate the query complexity based on how nested a field is (depth limitation)[/+] on Dec 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions