8000 Feature/custom layer operation support by martinstepanek · Pull Request #41 · slicknode/graphql-query-complexity · GitHub
[go: up one dir, main page]

Skip to content

Feature/custom layer operation support #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

martinstepanek
Copy link
Contributor
@martinstepanek martinstepanek commented Feb 3, 2021

Hi, thanks for nice library, it is really useful!

Although it probably wasn't original purpose of this library, we needed in our project to calculate and check asymptotic complexity of queries.
This can be done by multiplying instead of adding complexities on each level in query tree.


So field that has complexity of n can be set estimator like this:

complexity: ({ childComplexity }) => childComplexity * n

-> So if childComplexity is also n, asymptotic complexity is n^2


Problem is that there is addition inside of addComplexities function

complexityMap[type] + complexity

-> Fields on same level are added up.


So to solve this, I added layerTransformation: LayerTransformation optional option to getComplexity function to customize this behavior.


To calculate asymptotic complexity you can just pass your own transformation function:

 const complexity = getComplexity({
                    ...
                    layerTransformation: (a, b) => a * b;
                });

And to calculate asymptotic complexity on each query:

 const complexity = getComplexity({
                    ...
                    layerTransformation: (a, b, type) => {
                        if (type === 'Query') {
                            return a + b;
                        }
                        return a * b;
                    }
                });

Add layerTransformation option to support custom layer operation for computing final complexity
@ivome
Copy link
Collaborator
ivome commented Feb 8, 2021

That's an interesting idea. I think it would be nicer though to have this kind of functionality exposed in the estimator so that you can do this on a per-field basis (or a global estimator). You might have some parts in your graph where you need asymptotic complexity and others where you need the regular one.

This would enable this feature via directive or other estimators:

type Query {
   user: User @complexity(asymptotic: true)
}

A change like that would pretty sure break backwards compatibility, but if we add this, I think this is the way we should go. This would keep it more modular and extensible, and if we for example pass the SelectionSet together with the full tree of child complexities to the estimator, this would also enable all kinds of other use cases and functionality.

@martinstepanek
Copy link
Contributor Author

For "You might have some parts in your graph where you need asymptotic complexity and others where you need the regular one" use-case, type is passed to layerTransformation function so you can decide in which part of tree you wanna multiply and in which you wanna add, or do something different.

I don't understand how this could be applied on per-field basis as it is computed "between" fields (on the same level).

Enable this feature via directive as it is in example would be really strict and wouldn't allow much customization, because it allows only 2 options. And as I said, it is "between" fields, that means you cant assigned it to specific field.

With solution like this using layerTransformation function, you can in some cases multiply, but you also can multiply with coefficient or divide or do whatever you want.

Goal of this PR is not to allow computing asymptotic complexity, but to allow computing complexity in the way developer wants (asymptotic in my case)

@ivome
Copy link
Collaborator
ivome commented Feb 17, 2021

I don't understand how this could be applied on per-field basis as it is computed "between" fields (on the same level).

I was thinking of a step that combines the complexity where the context is not lost and you have some more information about what values you are multiplying/adding besides the type. (What field, number of total selected fields). This would enable more usecases besides calculating asymptotic complexity. Maybe some kind of complexity reducer for a selection set that can be customized.

Enable this feature via directive as it is in example would be really strict and wouldn't allow much customization, because it allows only 2 options. And as I said, it is "between" fields, that means you cant assigned it to specific field.

That was just an example, you could change the directive in any way you want, add enum types for multiple methods, write your own estimators, etc.
If combined with the idea of a complexity reducer that would provide the ability to calculate asymptotic complexity while enabling other use-cases as well.

Something like this:

type ComplexityReducer = (params: {
  type: GraphQLObjectType,
  selectionSet: SelectionSetWithComplexity
}) => number;

You would then have access to the number of selected fields, all complexity values of the entire selection set and child complexities, etc.

@ivome
Copy link
Collaborator
ivome commented Mar 18, 2021

With #46 being merged, the GraphQL node is now available inside of estimators. That way you have direct access to the selectionSet and can completely customize how complexities and child complexities are being calculated, also using asymptotic complexity. Only limitation: Since this is being calculated at the field level, this does not work for root query fields. For everything else you should be able to achieve what you're trying to do...

@ivome ivome closed this Apr 20, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
0