Relationships Traverse one-to-one, one-to-many, many-to-many, recursive, and polymorphic relationships. core reference core/relationships core/relationships.md

Relationships

Traverse one-to-one, one-to-many, many-to-many, recursive, and polymorphic relationships.

GraphJin relationship traversal comes from schema metadata. Foreign keys, configured relationships, embedded JSON virtual tables, remote API joins, and database-join edges all become nested GraphQL selections, but they are not all executed the same way.

Relationship kindExecution model
Same-database SQL joinCompiled into the generated SQL using joins, lateral subqueries, and JSON aggregation.
MongoDBRendered as JSON DSL and translated to aggregation pipeline stages such as $lookup, $match, $project, $unwind, and $group.
Remote API joinsParent rows are fetched first, then external operation results are merged into the response.
Cross databaseParent rows are fetched first, the join key is extracted, child queries run against the target database context, and placeholder fields are replaced in the JSON response.

Parent and child traversal

GraphQL
query {
  products(limit: 3, order_by: { id: asc }) {
    id
    owner {
      id
      email
    }
  }
}

Relationships come from database schema discovery and explicit table configuration.

Verified by Example_queryParentsWithChildren tests/query_test.go:623
Verified by Example_queryChildrenWithParent tests/query_test.go:650

Filters and ordering can target relationship paths when the path is known.

GraphQL
query {
  products(
    where: { owner: { id: { or: [{ eq: $user_id }, { eq: 3 }] } } }
    order_by: { users: { email: desc }, id: desc }
    limit: 5
  ) {
    id
    owner { id email }
  }
}

Verified by Example_queryWithWhereOnRelatedTable tests/query_test.go:504
Verified by Example_queryWithNestedOrderBy tests/query_test.go:279

Many-to-many

Join tables can expose many-to-many paths without user-written resolver code.

GraphQL
query {
  products(limit: 2, order_by: { id: asc }) {
    id
    customers {
      id
      email
    }
  }
}

When more than one join table or foreign key path is possible, use @through(table:) or @through(column:) to disambiguate. Agents should discover relationship rows in gj_catalog before guessing.

Verified by Example_queryManyToManyViaJoinTable1 tests/query_test.go:677
Verified by TestCompositeFK_ThroughColumn_EmitsFullJoinCondition core/internal/psql/psql_test.go:218

Recursive relationships

Self-referential tables can model trees such as comments and categories.

GraphQL
query {
  comments(id: $id) {
    id
    body
    replies: comments(find: "children") {
      id
      body
    }
  }
}
Verified by Example_queryWithRecursiveRelationship1 tests/query_test.go:1749

Polymorphic relationships

Union-style selections let GraphJin expose polymorphic subjects.

GraphQL
query {
  comments {
    id
    subject {
      ... on products {
        id
        name
      }
      ... on users {
        id
        email
      }
    }
  }
}
Verified by Example_queryWithUnionForPolymorphicRelationships tests/query_test.go:1111

MongoDB relationships

MongoDB relationships are not SQL joins. GraphJin emits JSON DSL and the MongoDB driver translates related selections to aggregation pipeline $lookup stages.

JSON
{
  "$lookup": {
    "from": "products",
    "let": { "userId": "$_id" },
    "pipeline": [
      { "$match": { "$expr": { "$eq": ["$owner_id", "$$userId"] } } }
    ],
    "as": "products"
  }
}

Array-column joins use $in; embedded JSON virtual tables use $unwind, $lookup, and $group rather than a simple lookup.

Verified by TestBuildCursorSeekFilterAsc mongodriver/query_cursor_test.go:86

Nested database joins

Cross-source joins preserve the same GraphQL shape but use different execution phases:

  1. Compile and execute the parent query against its source.
  2. Keep a placeholder field such as __orders_db_join in the parent JSON.
  3. Extract the parent key from the result.
  4. Build a child query such as orders(where: { user_id: { eq: 42 } }).
  5. Execute that child query with the target source’s compiler and connection.
  6. Replace the placeholder with the child JSON.

That model is why same-database joins are usually cheaper than cross-database joins, and why table/source ownership should be explicit in multi-source deployments.

Verified by TestBuildChildGraphQLQueryNestedDatabaseJoin core/multidb_test.go:809
Verified by TestDatabaseJoinFieldIds core/multidb_test.go:544

Docs