More fields for "WHERE" conditions in neo4j graphql federation

Hey folks,

I am using latest version of the neo4j graphql library v3.22.0 .

I have two graph DB and have a graphql associated with each of the DB , which I am federating using apollo federation gateway.

The problem is that there is one type definition called PATENT that is common in both the DB's and they are joined on the basis of a property called 'app_num'

This is the type def file for Biblio-DB

type PATENT  @key(fields : "app_num")  @shareable {
	actual_disposal_type: String
	aia: String
	app_num: String!
	app_status: String
	app_status_date: String
	attorney_docket_no: String
	confirmation_no: String
	disposal_type: String
	entity_status: String
	filing_date: String
	issue_date: String
	location: String
	patent_no: String
	publication_date: String
	publication_no: String
	title: String
	usc_class: String
	usc_subclass: String
	has_applicants: [APPLICANT!]! @relationship(type: "IS_APPLICANT_OF", direction: IN)
	has_attorneys: [ATTORNEY!]! @relationship(type: "IS_ASSOCIATED_TO_PATENT", direction: IN)
	has_examiner: [EXAMINER!]! @relationship(type: "EXAMINED", direction: IN)
	has_law_firm: [LAW_FIRM!]! @relationship(type: "IS_ASSOCIATED_TO", direction: IN)
	has_continuities: [CONTINUITY_DATA!]! @relationship(type: "HAS_CONTINUITY", direction: OUT)
	has_file_history : [FILE_NODE!]! @relationship(type: "HAS_FILE", direction: OUT)
	has_gau : [GAU!]! @relationship(type: "HAS_GAU", direction: OUT)
	has_priority_claims : [PRIORITY_CLAIM!]! @relationship(type: "HAS_PRIORITY_CLAIM", direction: OUT)
	has_prosecutions: [PROSECUTION_NODE!]! @relationship(type: "HAS_PROSECUTION", direction: OUT)
	has_term_adjustments: [TERM_ADJUSTMENTS!]! @relationship(type: "HAS_TERM_ADJUSTMENTS", direction: OUT)
	has_inventors: [INVENTOR!]! @relationship(type: "INVENTED", direction: IN)
	has_application_type: [APP_TYPE!]! @relationship(type: "IS_OF_TYPE", direction: OUT)
	has_parents: [PATENT!]! @relationship(type: "IS_PARENT_OF", direction: IN, properties: "IsParentOfProperties")
	has_children: [PATENT!]! @relationship(type: "IS_PARENT_OF", direction: OUT, properties: "IsParentOfProperties")
}

I have only added the relevant information from the schema file

This is the type def file for Assignment-DB

extend schema @link(url: "https://specs.apollo.dev/federation/v2.0",
          import: ["@key", "@shareable"])


type PATENT @key(fields: "app_num") @shareable{
	app_num: String! 
	has_assignments: [ASSIGNMENT!]! @relationship(type: "HAS_ASSIGNMENT", direction: OUT) 
}

type ASSIGNEE {
	address: String
	city: String
	country: String
	is_assignee_of_assignments: [ASSIGNMENT!]! @relationship(type: "IS_ASSIGNEE_OF", direction: OUT)
	name: String!
	postcode: String
	state: String
}

type ASSIGNMENT {
	has_assignees: [ASSIGNEE!]! @relationship(type: "IS_ASSIGNEE_OF", direction: IN)
	has_assignors: [ASSIGNOR!]! @relationship(type: "IS_ASSIGNOR_OF", direction: IN, properties: "IsAssignorOfProperties")
	conveyance_text: String
	correspondent: String
	correspondent_address: String
	country: String
	frame_no: String
	id: String!
	has_patents: [PATENT!]! @relationship(type: "HAS_ASSIGNMENT", direction: IN)
	recorded_date: String
	reel_no: String
}

type ASSIGNOR {
	is_assignor_of_assignments: [ASSIGNMENT!]! @relationship(type: "IS_ASSIGNOR_OF", direction: OUT, properties: "IsAssignorOfProperties")
	name: String!
}

interface IsAssignorOfProperties @relationshipProperties {
	execution_date: String
}

extend schema @mutation(operations: [])

Everything works fine if we use app_num for querying various types of data.

But I want that I can put a where condition on properties like 'patent_no' or the 'publication_no' from the biblio-db to fetch the assignment data.

For clarity,

query Patents($where: PATENTWhere) {
  patents(where: $where) {
    app_status
    has_assignments {
      conveyance_text
      has_patents {
        app_num
        app_status
        has_assignments {
          conveyance_text
        }
      }
    }
  }
}

I can use this type of query to fetch data if we use app_num in the "where" variable, I want the same functionality, but I want to pass patent_no or publication_no in "where" variable.

I think the library only writes "where" logic for the @shareable types only for the fields that are mentioned with the @key directive.

But If I mention any field like "patent_no" which does not exist in the assignment DB it gives an error while making an executable schema.

Shouldn't it fetch the app_num through the logic of biblio-db and then when it has the app_num then it should pass it to the assignment-db and fetch the corresponding data?

Am I missing something because it seems like an obvious feature to me?

Best Regards,
Aman Negi

Hi @aman.negi! The problem here is that both subgraphs produce an input type called PATENTWhere. I believe the issue is that the supergraph doesn't know how to combine these types and is just using the minimum overlap.

As you have two neo4j DBs you're also likely to face issues as the supergraph isn't very intelligent when multiple subgraphs have the same Query/Mutation fields. You'll likely notice that the query you posted will only return the data from one of your DBs and not the other. Additionally, you'll find createPatent mutations only insert data into one of your DBs and not the other.

Essentially, all this means is that you are likely to run into a lot of issues with naming collisions when using @neo4j/graphql for multiple subgraphs.

We've only released federation as an "experimental" feature currently and plan to address these issues before releasing this as a stable feature. Initially, we plan to allow you to add a prefix to your subgraphs e.g. "assignment". This would mean that types such as AssignmentPATENTWhere or query/mutation field such as assignmentPatents/createAssignmentPatents are generated so it is clear which DB should be hit.

Further down the line we also plan to allow you to rename generated queries/mutations to something that makes more sense e.g. createAssignmentPatents could become addAssignmentsToPatent as that's really what's happening.

For now you could try renaming the generated types manually using @graphql-tools/wrap

Hey @LiamDoodson ,
Thanks for the reply.

I get your point about the recreation of the input type 'PATENTWhere' but I wanted to know that is there a way that the library allows us to extend any type from a different sub graph.

Let me give you an example, in this biblio-subgraph, I defined a type called PATENT, like so :

type PATENT  @key(fields : "app_num")  @shareable {
	actual_disposal_type: String
	aia: String
	app_num: String!
	app_status: String
	app_status_date: String
	attorney_docket_no: String
	confirmation_no: String
	disposal_type: String
	entity_status: String
	filing_date: String
	issue_date: String
	location: String
	patent_no: String
	publication_date: String
	publication_no: String
	title: String
	usc_class: String
	usc_subclass: String
	has_applicants: [APPLICANT!]! @relationship(type: "IS_APPLICANT_OF", direction: IN)
	has_attorneys: [ATTORNEY!]! @relationship(type: "IS_ASSOCIATED_TO_PATENT", direction: IN)
	has_examiner: [EXAMINER!]! @relationship(type: "EXAMINED", direction: IN)
	has_law_firm: [LAW_FIRM!]! @relationship(type: "IS_ASSOCIATED_TO", direction: IN)
	has_continuities: [CONTINUITY_DATA!]! @relationship(type: "HAS_CONTINUITY", direction: OUT)
	has_file_history : [FILE_NODE!]! @relationship(type: "HAS_FILE", direction: OUT)
	has_gau : [GAU!]! @relationship(type: "HAS_GAU", direction: OUT)
	has_priority_claims : [PRIORITY_CLAIM!]! @relationship(type: "HAS_PRIORITY_CLAIM", direction: OUT)
	has_prosecutions: [PROSECUTION_NODE!]! @relationship(type: "HAS_PROSECUTION", direction: OUT)
	has_term_adjustments: [TERM_ADJUSTMENTS!]! @relationship(type: "HAS_TERM_ADJUSTMENTS", direction: OUT)
	has_inventors: [INVENTOR!]! @relationship(type: "INVENTED", direction: IN)
	has_application_type: [APP_TYPE!]! @relationship(type: "IS_OF_TYPE", direction: OUT)
	has_parents: [PATENT!]! @relationship(type: "IS_PARENT_OF", direction: IN, properties: "IsParentOfProperties")
	has_children: [PATENT!]! @relationship(type: "IS_PARENT_OF", direction: OUT, properties: "IsParentOfProperties")
}

And now I extend this type in a different sub-graph called BOS-subgraph, like so :

const axios = require("axios")
const { buildSubgraphSchema } = require('@apollo/subgraph');
const {gql} = require('graphql-tag');
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone')


async function startApolloServer() {
  const config =  { ssl: false, port: 7476, hostname: 'localhost' }
  const typeDefs = gql`
        type BosResponse {
          bos_action_date: String
          bos_action_subtype: String
          bos_action_type: String
          bos_app_abandoned_after_rejection: String
          bos_blocked_app_art_unit: String
          bos_blocked_app_assignee: String
          bos_blocked_app_id: String
          bos_blocked_app_short_status: String
          bos_blocked_app_status: String
          bos_blocked_app_title: String
          bos_blocked_pub_pat: String
          bos_blocking_app_art_unit: String
          bos_blocking_app_id: String
          bos_blocking_app_short_status: String
          bos_blocking_app_status: String
          bos_blocking_app_title: String
          bos_blocking_parsed: String
          bos_claim_amended: String
          bos_ifw_number: String
          input_blocking_number: String
        }

        extend type PATENT  @key(fields: "patent_no") {
          patent_no: String @external
          bos_data (patent_no : String) : [BosResponse]
        }

        extend type Query {
          bos (patent_no : String) : [BosResponse]
        }

    `

    // TODO check for patent number not null
  const resolvers = {
    PATENT: {
      bos_data: async (parent, args) => {
        const auth = {
          username: 'admin',
          password: 'd@rkn1gh7',
        };

        const headers = {
          'Content-Type': 'application/json',
        };

        payload = JSON.stringify({"patent_publication_list": [parent.patent_no]})

        const url = "http://192.168.0.38:9008/mini_bos"
        const response = await axios.post(url, payload, {
          headers: headers,
          auth: auth,
        }).catch((error) => {
          console.log(error)
        })
        
        // const temp = JSON.stringify(response.data)
        // console.log(response.data);
        if(response){
          return response.data
        }
        else{
          return null
        }
      }
    },
    Query: {
      
      bos: async (obj, args) => {
        // console.log(args)
        
        const auth = {
          username: 'admin',
          password: 'd@rkn1gh7',
        };

        const headers = {
          'Content-Type': 'application/json',
        };

        payload = JSON.stringify({"patent_publication_list": [args.patent_no]})

        const url = "http://192.168.0.38:9008/mini_bos"
        const response = await axios.post(url, payload, {
          headers: headers,
          auth: auth,
        }).catch((error) => {
          console.log(error)
        })
        
        // const temp = JSON.stringify(response.data)
        // console.log(response.data);
        return response.data
      }
    }
  }
  const schema = buildSubgraphSchema({typeDefs: typeDefs, resolvers: resolvers} )
  const server = new ApolloServer({
    schema : schema,
  });
  startStandaloneServer(server, { listen: { port: 7476 } });
  console.log('🚀 Server ready at port : 7476');
}

startApolloServer();

And this allows me to fetch the data provided by the BOS-subgraph, by any of the where conditions present in the PATENTWhere, even though the BOS-subgraph's resolver needs a field called 'patent_no', but it still works as apollo makes all those fields present in the PATENT node visible to the bos's resolver

So my question is, Is there a way to extend a type like this in the library right now ?

Best Regards,
Aman Negi