The first place to start is understanding how Fauna stores different kinds of relationships for GraphQL. The docs go into detail about this here. We can use that as precedence for managing relationships directly.
Data representation
Here, we are concerned about one-to-many relationships. In that case, Fauna’s GraphQL API stores the references that make up the relationships in the “many” side.
So, given a GraphQL schema like this:
type Owner {
name: String!
cars: [Car]! @relation
}
type Car {
plate: String!
owner: Owner @relation
the data will be stored like this:
// Owner
{
"ref": Ref(Collection("Owner"), "309362529386102849"),
"ts": 1631290049590000,
"data": {
"name": "Paul"
}
}
// Car
{
"ref": Ref(Collection("Car"), "320076030282825794"),
"ts": 1641507139410000,
"data": {
plate: "CCC-123",
owner: Ref(Collection("Owner"), "309362529386102849")
}
}
Since the relationship is stored in the many-side, set another Cars owner to the same value.
// Car
{
"ref": Ref(Collection("Car"), "32007603027489236458"),
"ts": 1641507139410000,
"data": {
plate: "DDD-555",
owner: Ref(Collection("Owner"), "309362529386102849")
}
}
“Connect” nested queries
To connect a Car to an Owner, set Car.owner to the Owner Ref. To remove the connection, set Car.owner to null
.
// connect car and owner
Update(Ref(Collection("Car"), "320076030282825794"), {
data: {
owner: Ref(Collection("Owner"), "309362529386102849")
}
})
// disconnect car and owner
Update(Ref(Collection("Car"), "320076030282825794"), {
data: {
owner: null
}
})
To create a Car with an Owner within the same transaction, make sure to set the Car.owner field.
// create car with owner
Create(Collection("Car"), {
data: {
plate: "CCC-123",
owner: Ref(Collection("Owner"), "309362529386102849")
}
})
“Create” nested queries
A “create” nested query could be done like this in GraphQL
mutation {
createCar(data: {
plate: "CCC-123"
owner: {
create: {
name: "Paul"
}
}
}) {
_id
plate
owner {
name
}
}
}
This can be done directly in FQL. Here we will use Create
to create a new Owner, which returns a Document. We then use Select
to pick the Ref from the Document.
// create car with new owner
Create(Collection("Car"), {
data: {
plate: "CCC-123",
owner: Select("ref",
Create(Collection("Owner"), {
data: {
name: "Paul"
}
})
)
}
})
Validation
In practice, you probably don’t want to just blindly store any Ref without checking if it exists. You would probably use the Exists
function to look for the Refs first.
// create car with owner
Create(Collection("Car"), {
data: {
plate: "CCC-123",
owner: Let(
{
ref: Ref(Collection("Owner"), "309362529386102849")
},
If(
Exists(Var("ref")),
Var("ref"),
// connect to nothing if the Owner Ref does not exist
null
// alternatively, you can abort with some custom error message
// Abort("Owner does not exist")
)
)
}
})
Query with Indexes
Since the relationship is stored within the Cars, in order to get a list of the Cars owned by a single Owner, we have to use an Index (Note this is how Fauna’s GraphQL API does it, too).
CreateIndex({
name: "car_owner_by_owner",
source: Collection("Car"),
terms: [{ field: ["data", "owner"] }]
})
Map(
Paginate(
Match(Index("car_owner_by_owner"), Ref(Collection("Owner"), "309362529386102849"))
),
Lambda("ref", Get(Var("ref")))
)
// returns
{
data: [
{
"ref": Ref(Collection("Car"), "320076030282825794"),
"ts": 1641507139410000,
"data": {
plate: "CCC-123",
owner: Ref(Collection("Owner"), "309362529386102849")
}
},
{
"ref": Ref(Collection("Car"), "32007603027489236458"),
"ts": 1641507139410000,
"data": {
plate: "DDD-555",
owner: Ref(Collection("Owner"), "309362529386102849")
}
}
]
}