[GH-ISSUE #16] How to merge queries? #107

Closed
opened 2026-03-15 11:30:38 +03:00 by kerem · 9 comments
Owner

Originally created by @dChunikhin on GitHub (Jul 2, 2019).
Original GitHub issue: https://github.com/atulmy/gql-query-builder/issues/16

Let's assume:

i have query:

    gql.query({
        operation: 'user',
        fields: ['name'],
    });

and one more:

    gql.query({
        operation: 'profile',
        fields: ['id', 'photo'],
    });

Everything is nice now.

But what if i need to get both user and profile in one query? What should I do? Is there mechanism to merge these queries. Especially, due to the fact, that one of the best features of GraphQL (comparing with REST) is to combine queries into one.

Originally created by @dChunikhin on GitHub (Jul 2, 2019). Original GitHub issue: https://github.com/atulmy/gql-query-builder/issues/16 Let's assume: i have query: ``` gql.query({ operation: 'user', fields: ['name'], }); ``` and one more: ``` gql.query({ operation: 'profile', fields: ['id', 'photo'], }); ``` Everything is nice now. But what if i need to get both user and profile in one query? What should I do? Is there mechanism to merge these queries. Especially, due to the fact, that one of the best features of GraphQL (comparing with REST) is to combine queries into one.
kerem 2026-03-15 11:30:38 +03:00
Author
Owner

@atulmy commented on GitHub (Jul 2, 2019):

Do you mean following case:

const query = queryBuilder.query([
  {
    operation: "thoughts",
    fields: ["id", "name", "thought"]
  },
  {
    operation: "prayers",
    fields: ["id", "name", "prayer"]
  }
]);

// Output
query  { 
  thoughts  { id, name, thought } 
  prayers  { id, name, prayer } 
}
<!-- gh-comment-id:507709325 --> @atulmy commented on GitHub (Jul 2, 2019): Do you mean following case: ```javascript const query = queryBuilder.query([ { operation: "thoughts", fields: ["id", "name", "thought"] }, { operation: "prayers", fields: ["id", "name", "prayer"] } ]); // Output query { thoughts { id, name, thought } prayers { id, name, prayer } } ```
Author
Owner

@dChunikhin commented on GitHub (Jul 2, 2019):

Yes, thanks, it helped me. But there is an issue with 'variables' object: all of your queries have to contain variables. If there's no, "Variable "$uri" is not defined." will be as a response. Even if one of your queries doesn't need this variable. I found way to avoid this: place queries that don't need variables in the beginning of the list, and it will work fine. But it smells like hardcode.

<!-- gh-comment-id:507723001 --> @dChunikhin commented on GitHub (Jul 2, 2019): Yes, thanks, it helped me. But there is an issue with 'variables' object: all of your queries have to contain variables. If there's no, "Variable "$uri" is not defined." will be as a response. Even if one of your queries doesn't need this variable. I found way to avoid this: place queries that don't need variables in the beginning of the list, and it will work fine. But it smells like hardcode.
Author
Owner

@atulmy commented on GitHub (Jul 2, 2019):

Thanks, I'll look into it.

<!-- gh-comment-id:507740132 --> @atulmy commented on GitHub (Jul 2, 2019): Thanks, I'll look into it.
Author
Owner

@dChunikhin commented on GitHub (Jul 3, 2019):

In addition:

query([
                {
                    operation: 'user',
                    fields: ['name'],
                    variables: { id: 13 },
                },
                {
                    operation: 'gallery',
                    fields: ['id'],
                    variables: { idx: 13 },
                },
        ]),

output:

{ 
    query: "query ($idx: Int) { user (id: $id) { name } gallery (idx: $idx) { id } }",
    variables: {idx: 13}
}

Notice, that variables has only one variable - idx (latest). I suppose, that merging operation of all variables of all quieries have to be here. For instance, Object.assign method gotta be used.

<!-- gh-comment-id:508056149 --> @dChunikhin commented on GitHub (Jul 3, 2019): In addition: ``` query([ { operation: 'user', fields: ['name'], variables: { id: 13 }, }, { operation: 'gallery', fields: ['id'], variables: { idx: 13 }, }, ]), ``` output: ``` { query: "query ($idx: Int) { user (id: $id) { name } gallery (idx: $idx) { id } }", variables: {idx: 13} } ``` Notice, that **variables** has only one variable - idx (latest). I suppose, that merging operation of all **variables** of all quieries have to be here. For instance, Object.assign method gotta be used.
Author
Owner

@dChunikhin commented on GitHub (Jul 4, 2019):

Bro, I've sorted out this issue in your code. But due to the fact I am in a scrum team, and my time runs off, I can't fix your lib and create a pull request. So, just listen if you are interested in the solution.
You used State Machine pattern (something like this). Your default adapter takes queries from its arguments, destructing them into three fields: operation, fields, variables. Then produces some actions (operationTemplate, queryDataNameAndArgumentMap) on them, relying on what these fields contain of. This way, your adapter builds first query. Then, it do the same with next query in the queries list (IQueryBuilderOptions[]). Each next query overrides fields (operation, fields, variables) by his own properties and it works well, but... When all queries were processed, general wrapping operation (operationWrapperTemplate) is involved. And it relies on these fields too, for creating the argument-list (query {...}) but there's no just list of all arguments, that all queries provide, but only one (the last), so it makes decision, that there's only one variable in the query, so return the incorrect query-string (query ($idx: Int) { user (id: $id) { name } gallery (idx: $idx) { id } }). The same problem occurs in the operation of returning a list of used variables (Utils.queryVariablesMap), so it returns only variables for the last query in the provided list (IQueryBuilderOptions[]), not for all of them, as I expect working with your lib. So, replace state machine with more common way: this.variables: from { queryVariables } to this.variables: [{ queryVariables }, { queryVariables }] and rely on its content to build query params, variables used in queries and it will be more clear, i think. If smth isn't understandable, lemme know.

<!-- gh-comment-id:508503114 --> @dChunikhin commented on GitHub (Jul 4, 2019): Bro, I've sorted out this issue in your code. But due to the fact I am in a scrum team, and my time runs off, I can't fix your lib and create a pull request. So, just listen if you are interested in the solution. You used State Machine pattern (something like this). Your default adapter takes queries from its arguments, destructing them into three fields: operation, fields, variables. Then produces some actions (_operationTemplate_, _queryDataNameAndArgumentMap_) on them, relying on what these fields contain of. This way, your adapter builds first query. Then, it do the same with next query in the queries list (_IQueryBuilderOptions[]_). Each next query overrides fields (operation, fields, variables) by his own properties and it works well, but... When all queries were processed, general wrapping operation (_operationWrapperTemplate_) is involved. And it relies on these fields too, for creating the argument-list (_query <this-one> {...}_) but there's no just list of all arguments, that all queries provide, but only one (the last), so it makes decision, that there's only one variable in the query, so return the incorrect query-string (_query ($idx: Int) { user (id: $id) { name } gallery (idx: $idx) { id } }_). The same problem occurs in the operation of returning a list of used variables (_Utils.queryVariablesMap_), so it returns only variables for the last query in the provided list (_IQueryBuilderOptions[]_), not for all of them, as I expect working with your lib. So, replace state machine with more common way: this.variables: from { queryVariables } to this.variables: [{ queryVariables }, { queryVariables }] and rely on its content to build query params, variables used in queries and it will be more clear, i think. If smth isn't understandable, lemme know.
Author
Owner

@atulmy commented on GitHub (Jul 4, 2019):

I see, can you post the code snippet which resolves this issue? I can commit and publish the fixes.

<!-- gh-comment-id:508504971 --> @atulmy commented on GitHub (Jul 4, 2019): I see, can you post the code snippet which resolves this issue? I can commit and publish the fixes.
Author
Owner

@dChunikhin commented on GitHub (Jul 4, 2019):

It needs good solution, but it takes too much time for me. You have to change the flow of data in your adapter. Just build the object with all of queries we pass, and then transform it to the query-string. Not one-by-one query, as you do now.

You have created good product, so I hope you'll maintain then.

<!-- gh-comment-id:508506369 --> @dChunikhin commented on GitHub (Jul 4, 2019): It needs good solution, but it takes too much time for me. You have to change the flow of data in your adapter. Just build the object with all of queries we pass, and then transform it to the query-string. Not one-by-one query, as you do now. You have created good product, so I hope you'll maintain then.
Author
Owner

@toadkicker commented on GitHub (Jul 8, 2019):

@atulmy when I was refactoring the query generation code into the default adapter I had similar thinking. I think @dChunikhin is suggesting the functions in there not be dependent on one-another (e.g. don't call a function from another internal function)

The idea then is that all the functions must return a string, and that string is concatenated together by a single builder and pushed into the state of the class.

It would be a pretty intrusive refactor to do but it would make maintaining the adapter(s) much more sane. Maybe make a new module for helper methods that was started with the Utils class.

<!-- gh-comment-id:509395888 --> @toadkicker commented on GitHub (Jul 8, 2019): @atulmy when I was refactoring the query generation code into the default adapter I had similar thinking. I think @dChunikhin is suggesting the functions in there not be dependent on one-another (e.g. don't call a function from another internal function) The idea then is that all the functions must return a string, and that string is concatenated together by a single builder and pushed into the state of the class. It would be a pretty intrusive refactor to do but it would make maintaining the adapter(s) much more sane. Maybe make a new module for helper methods that was started with the Utils class.
Author
Owner

@Devorein commented on GitHub (Jun 29, 2020):

I've fixed the multiple query variables issue that @dChunikhin was talking about in my PR.

<!-- gh-comment-id:651047752 --> @Devorein commented on GitHub (Jun 29, 2020): I've fixed the multiple query variables issue that @dChunikhin was talking about in my [PR](https://github.com/atulmy/gql-query-builder/pull/39).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/gql-query-builder#107
No description provided.