2020-01-14 15:57:45 +03:00
|
|
|
.. meta::
|
|
|
|
:description: Model one-to-one relationships in Hasura
|
|
|
|
:keywords: hasura, docs, schema, relationship, one-to-one, 1-1
|
|
|
|
|
2019-12-26 15:05:37 +03:00
|
|
|
.. _one_to_one_modelling:
|
|
|
|
|
2018-11-06 11:38:40 +03:00
|
|
|
Modelling one-to-one table relationships
|
|
|
|
========================================
|
|
|
|
|
2018-12-03 15:12:24 +03:00
|
|
|
.. contents:: Table of contents
|
|
|
|
:backlinks: none
|
|
|
|
:depth: 1
|
|
|
|
:local:
|
|
|
|
|
2020-07-08 00:47:42 +03:00
|
|
|
Introduction
|
|
|
|
------------
|
|
|
|
|
2019-09-11 10:17:14 +03:00
|
|
|
A ``one-to-one`` relationship between two tables can be established via a **unique foreign key constraint**.
|
2018-11-06 11:38:40 +03:00
|
|
|
|
|
|
|
Say we have the following two tables in our database schema:
|
|
|
|
|
|
|
|
.. code-block:: sql
|
|
|
|
|
|
|
|
author (
|
2020-05-12 13:47:06 +03:00
|
|
|
id SERIAL PRIMARY KEY,
|
2018-11-06 11:38:40 +03:00
|
|
|
name TEXT
|
|
|
|
)
|
|
|
|
|
|
|
|
passport_info (
|
2020-05-12 13:47:06 +03:00
|
|
|
id SERIAL PRIMARY KEY,
|
2019-09-05 15:51:00 +03:00
|
|
|
owner_id INT NOT NULL
|
2018-11-06 11:38:40 +03:00
|
|
|
passport_number TEXT
|
|
|
|
...
|
|
|
|
)
|
|
|
|
|
2019-09-11 10:17:14 +03:00
|
|
|
These two tables are related via a ``one-to-one`` relationship. i.e.:
|
2018-11-06 11:38:40 +03:00
|
|
|
|
|
|
|
- an ``author`` can have one ``passport_info``
|
|
|
|
- a ``passport_info`` has one ``owner``
|
|
|
|
|
2020-07-08 00:47:42 +03:00
|
|
|
Step 1: Set up a table relationship in the database
|
|
|
|
---------------------------------------------------
|
2018-12-03 15:12:24 +03:00
|
|
|
|
2018-11-06 11:38:40 +03:00
|
|
|
This ``one-to-one`` relationship can be established in the database by:
|
|
|
|
|
2019-09-11 10:17:14 +03:00
|
|
|
1. Adding a **foreign key constraint** from the ``passport_info`` table to the ``author`` table using the ``owner_id``
|
2018-11-23 12:29:03 +03:00
|
|
|
and ``id`` columns of the tables respectively
|
|
|
|
2. Adding a **unique constraint** to the ``owner_id`` column for the ``passport_info`` table
|
2018-11-06 11:38:40 +03:00
|
|
|
|
|
|
|
|
2019-09-11 10:17:14 +03:00
|
|
|
This will ensure that the value of the ``owner_id`` column in ``passport_info`` table is present in the ``id`` column of
|
2018-11-06 11:38:40 +03:00
|
|
|
the ``author`` table and there will be only one row with a particular ``owner_id``.
|
|
|
|
|
2020-07-08 00:47:42 +03:00
|
|
|
Step 2: Set up GraphQL relationships
|
|
|
|
------------------------------------
|
2018-12-03 15:12:24 +03:00
|
|
|
|
2020-03-11 22:42:36 +03:00
|
|
|
To access the nested objects via the GraphQL API, :ref:`create the following relationships <create_relationships>`:
|
2018-11-06 11:38:40 +03:00
|
|
|
|
2019-09-11 10:17:14 +03:00
|
|
|
- Object relationship, ``passport_info`` from the ``author`` table using ``id -> passport_info :: owner_id``
|
|
|
|
- Object relationship, ``owner`` from the ``passport_info`` table using ``owner_id -> author :: id``
|
2018-11-06 11:38:40 +03:00
|
|
|
|
2021-02-02 11:44:36 +03:00
|
|
|
Query using one-to-one relationships
|
|
|
|
------------------------------------
|
2018-12-03 15:12:24 +03:00
|
|
|
|
2018-11-06 11:38:40 +03:00
|
|
|
We can now:
|
|
|
|
|
2021-02-02 11:44:36 +03:00
|
|
|
- fetch a list of ``authors`` with their ``passport_info``:
|
2018-11-06 11:38:40 +03:00
|
|
|
|
|
|
|
.. graphiql::
|
|
|
|
:view_only:
|
|
|
|
:query:
|
|
|
|
query {
|
|
|
|
author {
|
|
|
|
id
|
|
|
|
name
|
|
|
|
passport_info {
|
|
|
|
id
|
|
|
|
passport_number
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
:response:
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"author": [
|
|
|
|
{
|
|
|
|
"id": 1,
|
|
|
|
"name": "Justin",
|
|
|
|
"passport_info": {
|
|
|
|
"id": 1,
|
|
|
|
"passport_number": "987456234"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": 2,
|
|
|
|
"name": "Beltran",
|
|
|
|
"passport_info": {
|
|
|
|
"id": 2,
|
|
|
|
"passport_number": "F0004586"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-02 11:44:36 +03:00
|
|
|
- fetch a list of ``passport_infos`` with their ``owner``:
|
2018-11-06 11:38:40 +03:00
|
|
|
|
|
|
|
.. graphiql::
|
|
|
|
:view_only:
|
|
|
|
:query:
|
|
|
|
query {
|
|
|
|
passport_info {
|
|
|
|
id
|
|
|
|
passport_number
|
|
|
|
owner {
|
|
|
|
id
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
:response:
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"passport_info": [
|
|
|
|
{
|
|
|
|
"id": 1,
|
|
|
|
"passport_number": "987456234",
|
|
|
|
"owner": {
|
|
|
|
"id": 1,
|
|
|
|
"name": "Justin"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": 2,
|
|
|
|
"passport_number": "F0004586",
|
|
|
|
"owner": {
|
|
|
|
"id": 2,
|
|
|
|
"name": "Beltran"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
2019-09-05 15:51:00 +03:00
|
|
|
|
2021-02-02 11:44:36 +03:00
|
|
|
Insert using one-to-one relationships
|
|
|
|
-------------------------------------
|
|
|
|
|
|
|
|
We can now:
|
|
|
|
|
|
|
|
- insert ``passport_info`` with their ``owner`` where the ``owner`` might already exist (assume unique ``name`` for ``owner``):
|
|
|
|
|
|
|
|
.. graphiql::
|
|
|
|
:view_only:
|
|
|
|
:query:
|
|
|
|
mutation upsertPassportInfoWithOwner {
|
|
|
|
insert_passport_info(objects: [
|
|
|
|
{
|
|
|
|
passport_number: "X98973765",
|
|
|
|
owner: {
|
|
|
|
data: {
|
|
|
|
name: "Kelly"
|
|
|
|
},
|
|
|
|
on_conflict: {
|
|
|
|
constraint: owner_name_key,
|
|
|
|
update_columns: [name]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]) {
|
|
|
|
returning {
|
|
|
|
passport_number
|
|
|
|
owner {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
:response:
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"insert_passport_info": {
|
|
|
|
"returning": [
|
|
|
|
{
|
|
|
|
"passport_number": "X98973765",
|
|
|
|
"owner": {
|
|
|
|
"name": "Kelly"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
You can avoid the ``on_conflict`` clause if you will never have conflicts.
|
|
|
|
|
|
|
|
|
|
|
|
Current limitations with nested one-to-one mutations
|
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
2019-09-05 15:51:00 +03:00
|
|
|
|
|
|
|
With one-to-one relationships, currently nested mutations will work only in one of the two directions.
|
|
|
|
|
|
|
|
In our example, inserting a ``passport_info`` with their nested ``owner`` will work seamlessly but trying to
|
|
|
|
insert an ``author`` with their nested ``passport_info`` will throw a constraint violation error.
|
|
|
|
|
|
|
|
This is due to the way Hasura GraphQL engine currently handles nested mutations (described in detail
|
|
|
|
:ref:`here <nested_inserts>`). As nested object relations are inserted before the parent, the ``passport_info``
|
|
|
|
will be attempted to be inserted first and the value of its ``owner_id`` will be attempted to be set as the
|
|
|
|
``id`` of the ``author``. Due to this, based on whether the ``owner_id`` of ``passport_info`` is nullable or not, a
|
|
|
|
``Not-NULL violation`` error will be thrown either for the ``owner_id`` field of ``passport_info`` or the ``id``
|
|
|
|
field of ``author``.
|