Vincit / objection-graphql

GraphQL schema generator for objection.js
MIT License
307 stars 34 forks source link

Error when merging two schemas with graphql-tools mergeSchemas #27

Closed nasushkov closed 6 years ago

nasushkov commented 6 years ago

I'm trying to merge two objection-graphql schemas with graphql-tools mergeSchemas function (aka schema stitching). Each schema works ok independently, however any query to the merged schema results in the following error:

"undefined passed as argument #2 for 'where' operation. Call skipUndefined() method to ignore the undefined values."

The error is the same for any query including even the basic ones like:

{
    companys {
      id, 
      name
    }
 }

I know that the error can reside somewhere in graphql-tools but what I see is that it actually delegating the query to the appropriate schema depending on type, i.e.:

 delegate: function (operation, fieldName, args, context, info) {
            var schema = typeRegistry.getSchemaByField(operation, fieldName);
            if (!schema) {
                throw new Error("Cannot find subschema for root field " + operation + "." + fieldName);
            }
            var fragmentReplacements = typeRegistry.fragmentReplacements;
            return delegateToSchema(schema, fragmentReplacements, operation, fieldName, args, context, info);
        }

Any ideas what how to fix this?

koskimas commented 6 years ago

Sorry, no idea.

vjpr commented 6 years ago

@nasushkov I am having exact same issue. Did you find a fix?

nasushkov commented 6 years ago

@vjpr Unfortunately, not. Right now using two separate schemas. However, I plan to investigate this issue in the future. I'd also appreciate if you have any thoughts on this.

vjpr commented 6 years ago

@nasushkov After some digging it seems that its an issue in the graphql-tools mergeSchemas library.

My best guess is its a bug inside processRootField, which is where the argument that you have specified in the query seems to be lost.

Below, variables is being returned with all the possible variables, except the variable that was specified in the query. I can't figure out what this function is doing.

function processRootField(selection, rootFieldName, rootField) {
    var existingArguments = selection.arguments || [];
    var existingArgumentNames = existingArguments.map(function (arg) { return arg.name.value; });
    var allowedArguments = rootField.args.map(function (arg) { return arg.name; });
    var missingArgumentNames = difference(allowedArguments, existingArgumentNames);
    var extraArguments = difference(existingArgumentNames, allowedArguments);
    var filteredExistingArguments = existingArguments.filter(function (arg) { return extraArguments.indexOf(arg.name.value) === -1; });
    var variables = [];

    console.log({existingArguments,
    existingArgumentNames,
    allowedArguments,
    missingArgumentNames,
    extraArguments,
    filteredExistingArguments,
    variables })

    var missingArguments = missingArgumentNames.map(function (name) {
        // (XXX): really needs better var generation
        var variableName = "_" + name;
        variables.push({
            arg: name,
            variable: variableName,
        });
        return {
            kind: graphql_1.Kind.ARGUMENT,
            name: {
                kind: graphql_1.Kind.NAME,
                value: name,
            },
            value: {
                kind: graphql_1.Kind.VARIABLE,
                name: {
                    kind: graphql_1.Kind.NAME,
                    value: variableName,
                },
            },
        };
    });
    return {
        selection: {
            kind: graphql_1.Kind.FIELD,
            alias: null,
            arguments: filteredExistingArguments.concat(missingArguments),
            selectionSet: selection.selectionSet,
            name: {
                kind: graphql_1.Kind.NAME,
                value: rootFieldName,
            },
        },
        variables: variables,
    };
}

@freiksenet Can you help us out here with what processRootFields is suppose to do? And why variables is removing our only specified variable.

vjpr commented 6 years ago

@freiksenet

Response

{
  "data": {
    "assets": null
  },
  "errors": [
    {
      "message": "undefined passed as argument #2 for 'where' operation. Call skipUndefined() method to ignore the undefined values.",
      "locations": [
        {
          "line": 1,
          "column": 2
        }
      ],
      "path": [
        "assets"
      ]
    }
  ]
}

Input query

{assets(deviceIdLt: "1"){
  deviceId
  data {
    type
    containerId
    articleId
  }
}}

Modified query

query ($_deviceId: String, $_deviceIdEq: String, $_deviceIdGt: String, $_deviceIdGte: String, $_deviceIdLte: String, $_deviceIdLike: String, $_deviceIdIsNull: Boolean, $_deviceIdIn: [String], $_deviceIdNotIn: [String], $_deviceIdLikeNoCase: String, $_orderBy: AssetPropertiesEnum, $_orderByDesc: AssetPropertiesEnum, $_range: [Int], $_TypeEq: Int, $_ArticleIdEq: String, $_ContainerIdEq: Int) {
  assets(deviceIdLt: "1", deviceId: $_deviceId, deviceIdEq: $_deviceIdEq, deviceIdGt: $_deviceIdGt, deviceIdGte: $_deviceIdGte, deviceIdLte: $_deviceIdLte, deviceIdLike: $_deviceIdLike, deviceIdIsNull: $_deviceIdIsNull, deviceIdIn: $_deviceIdIn, deviceIdNotIn: $_deviceIdNotIn, deviceIdLikeNoCase: $_deviceIdLikeNoCase, orderBy: $_orderBy, orderByDesc: $_orderByDesc, range: $_range, TypeEq: $_TypeEq, ArticleIdEq: $_ArticleIdEq, ContainerIdEq: $_ContainerIdEq) {
    deviceId
    data {
      type
      containerId
      articleId
    }
  }
}

{ GraphQLError: undefined passed as argument #2 for 'where' operation. Call skipUndefined() method to ignore the undefined values.
vjpr commented 6 years ago

@koskimas After schema stitching there are undefined arguments added to the query (as seen above in the modified query). Could you support this in objection-graphql?