Add JSON, Composite and Ranges types to the working with types how-to
This commit is contained in:
@@ -14,6 +14,8 @@ Community Tutorials
|
||||
* `"CodeLess" backend using postgres, postgrest and oauth2 authentication with keycloak <https://www.mathieupassenaud.fr/codeless_backend/>`_ -
|
||||
A step-by-step tutorial for using PostgREST with KeyCloak(hosted on a managed service).
|
||||
|
||||
* `How PostgreSQL triggers work when called with a PostgREST PATCH HTTP request <https://blog.fgribreau.com/2020/11/how-postgresql-triggers-works-when.html>`_ - A tutorial to see how the old and new values are set or not when doing a PATCH request to PostgREST.
|
||||
|
||||
.. _templates:
|
||||
|
||||
Templates
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
Casting a type to a custom JSON object
|
||||
======================================
|
||||
|
||||
:author: `steve-chavez <https://github.com/steve-chavez>`_
|
||||
|
||||
While using PostgREST you might have noticed that certain PostgreSQL types translate to JSON strings when you would
|
||||
have expected a JSON object or array. For example, let's see the case of `range types <https://www.postgresql.org/docs/current/rangetypes.html>`_.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
-- example taken from https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-EXAMPLES
|
||||
create table reservations (
|
||||
room int
|
||||
, during tsrange
|
||||
);
|
||||
|
||||
insert into
|
||||
reservations
|
||||
values
|
||||
(1108, tsrange('2010-01-01 14:30', '2010-01-01 15:30'));
|
||||
|
||||
Here we have a column named **during** as a ``tsrange`` type, we would like to get it as JSON through PostgREST.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/reservations"
|
||||
|
||||
Result:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"room":1108,
|
||||
"during":"[\"2010-01-01 14:30:00\",\"2010-01-01 15:30:00\")"
|
||||
}
|
||||
]
|
||||
|
||||
The **during** value is probably not the in the format you want. We get a JSON string because by default PostgreSQL casts
|
||||
the type to JSON by using its ``text`` representation. We can change this representation to a custom JSON object by `creating a CAST <https://www.postgresql.org/docs/current/sql-createcast.html>`_ .
|
||||
|
||||
To do this, first we'll define the function that will do the conversion from ``tsrange`` to ``json``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function tsrange_to_json(tsrange) returns json as $$
|
||||
select json_build_object(
|
||||
'lower', lower($1)
|
||||
, 'upper', upper($1)
|
||||
, 'lower_inc', lower_inc($1)
|
||||
, 'upper_inc', upper_inc($1)
|
||||
);
|
||||
$$ language sql;
|
||||
|
||||
Using this function we'll create the CAST.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create cast (tsrange as json) with function tsrange_to_json(tsrange) as assignment;
|
||||
|
||||
And we'll do the request and :ref:`cast the column <casting_columns>`.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/reservations?select=room,during::json"
|
||||
|
||||
The result now is:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"room":1108,
|
||||
"during":{
|
||||
"lower" : "2010-01-01T14:30:00",
|
||||
"upper" : "2010-01-01T15:30:00",
|
||||
"lower_inc" : true,
|
||||
"upper_inc" : false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
You can use the same idea for creating custom casts for different types.
|
||||
|
||||
.. note::
|
||||
|
||||
If you don't want to modify casts for built-in types, an option would be to `create a custom type <https://www.postgresql.org/docs/current/sql-createtype.html>`_
|
||||
for your own ``tsrange`` and add its own cast.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type mytsrange as range (subtype = timestamp, subtype_diff = tsrange_subdiff);
|
||||
|
||||
-- define column types and casting function analoguously to the above example
|
||||
-- ...
|
||||
|
||||
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
|
||||
@@ -1,81 +0,0 @@
|
||||
Embedding a table from another schema
|
||||
=====================================
|
||||
|
||||
:author: `steve-chavez <https://github.com/steve-chavez>`_
|
||||
|
||||
Suppose you have a **people** table in the ``public`` schema and this schema is exposed through PostgREST's :ref:`db-schemas`.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table public.people(
|
||||
id int primary key
|
||||
, full_name text
|
||||
);
|
||||
|
||||
And you want to :ref:`embed <resource_embedding>` the **people** table with a **details** table that's in another schema named ``private``.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create schema if not exists private;
|
||||
|
||||
-- For simplicity's sake the table is devoid of constraints/domains on email, phone, etc.
|
||||
create table private.details(
|
||||
id int primary key references public.people
|
||||
, email text
|
||||
, phone text
|
||||
, birthday date
|
||||
, occupation text
|
||||
, company text
|
||||
);
|
||||
|
||||
-- other database objects in this schema
|
||||
-- ...
|
||||
-- ...
|
||||
|
||||
To solve this, you can create a view of **details** in the ``public`` schema. We'll call it **public_details**.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create view public.public_details as
|
||||
select
|
||||
id
|
||||
, occupation
|
||||
, company
|
||||
from
|
||||
private.details;
|
||||
|
||||
Since PostgREST supports :ref:`embedding_views`, you can embed **people** with **public_details**.
|
||||
|
||||
Let's insert some data to test this:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
insert into
|
||||
public.people
|
||||
values
|
||||
(1, 'John Doe'), (2, 'Jane Doe');
|
||||
|
||||
insert into
|
||||
private.details
|
||||
values
|
||||
(1, 'jhon@fake.com', '772-323-5433', '1990-02-01', 'Transportation attendant', 'Body Fate'),
|
||||
(2, 'jane@fake.com', '480-474-6571', '1980-04-21', 'Geotechnical engineer', 'Earthworks Garden Kare');
|
||||
|
||||
.. important::
|
||||
|
||||
Make sure PostgREST's schema cache is up-to-date. See :ref:`schema_reloading`.
|
||||
|
||||
Now, make the following request:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl "http://localhost:3000/people?select=full_name,public_details(occupation,company)"
|
||||
|
||||
The result should be:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{"full_name":"John Doe","public_details":[{"occupation":"Transportation attendant","company":"Body Fate"}]},
|
||||
{"full_name":"Jane Doe","public_details":[{"occupation":"Geotechnical engineer","company":"Earthworks Garden Kare"}]}
|
||||
]
|
||||
@@ -79,6 +79,259 @@ You can use other comparative filters and also `PostgreSQL special date/time inp
|
||||
}
|
||||
]
|
||||
|
||||
JSON
|
||||
----
|
||||
|
||||
To work with a ``json`` type column, you can handle the value as a JSON object. For instance, let's use this table:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table products (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
extra_info json
|
||||
);
|
||||
|
||||
Now, you can insert a new product using a JSON object for the ``extra_info`` column:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /products HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Canned fish",
|
||||
"extra_info": {
|
||||
"expiry_date": "2025-12-31",
|
||||
"exportable": true
|
||||
}
|
||||
}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Canned fish",
|
||||
"extra_info": {
|
||||
"expiry_date": "2025-12-31",
|
||||
"exportable": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
To query and filter the data see :ref:`json_columns` for a complete reference.
|
||||
|
||||
Composite Types
|
||||
---------------
|
||||
|
||||
With PostgREST, you have two options to handle `composite type columns <https://www.postgresql.org/docs/current/rowtypes.html>`_. On one hand you can use string representation and on the other you can handle it as you would a JSON column. Let's create a type and a table for this example:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type dimension as (
|
||||
length decimal(6,2),
|
||||
width decimal (6,2),
|
||||
height decimal (6,2),
|
||||
unit text
|
||||
);
|
||||
|
||||
create table products (
|
||||
id int primary key,
|
||||
size dimension
|
||||
);
|
||||
|
||||
insert into products (id, size)
|
||||
values (1, '(5.0,5.0,10.0,"cm")');
|
||||
|
||||
Now, you could insert values using string representation as seen in the example above.
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /products HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{ "id": 2, "size": "(0.7,0.5,1.8,\"m\")" }
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{ "id": 2, "size": "(0.7,0.5,1.8,\"m\")" }
|
||||
EOF
|
||||
|
||||
Or, you could insert the data in JSON format. The following request is equivalent to the previous one:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /products HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 2,
|
||||
"size": {
|
||||
"length": 0.7,
|
||||
"width": 0.5,
|
||||
"height": 1.8,
|
||||
"unit": "m"
|
||||
}
|
||||
}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/products" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 2,
|
||||
"size": {
|
||||
"length": 0.7,
|
||||
"width": 0.5,
|
||||
"height": 1.8,
|
||||
"unit": "m"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
You can also query data using the arrow operators as you would for :ref:`JSON columns <json_columns>`.
|
||||
|
||||
Ranges
|
||||
------
|
||||
|
||||
To illustrate how to work with `ranges <https://www.postgresql.org/docs/current/rangetypes.html>`_, let's use the following table as an example:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create table events (
|
||||
id int primary key,
|
||||
name text unique,
|
||||
duration tsrange
|
||||
);
|
||||
|
||||
Now, to insert a new event, specify the ``duration`` value as a string representation of the ``tsrange`` type, for example:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
POST /events HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
|
||||
}
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/events" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": "['2022-12-31 11:00','2023-01-01 06:00']"
|
||||
}
|
||||
EOF
|
||||
|
||||
You can use range :ref:`operators <operators>` to filter the data. But what if you need get the events for the New Year 2023? Doing this filter ``events?duration=cs.2023-01-01`` will return an error because PostgreSQL needs an explicit cast to timestamp of the string value. A workaround would be to use a range starting and ending in the same date, like this:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /events?duration=cs.[2023-01-01,2023-01-01] HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/events?duration=cs.\[2023-01-01,2023-01-01\]"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": "[\"2022-12-31 11:00:00\",\"2023-01-01 06:00:00\"]"
|
||||
}
|
||||
]
|
||||
|
||||
.. _casting_range_to_json:
|
||||
|
||||
Casting a Range to a JSON Object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As you may have noticed, the ``tsrange`` value is returned as a string literal. To return it as a JSON value, first you need to create a function that will do the conversion from a ``tsrange`` type:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create or replace function tsrange_to_json(tsrange) returns json as $$
|
||||
select json_build_object(
|
||||
'lower', lower($1)
|
||||
, 'upper', upper($1)
|
||||
, 'lower_inc', lower_inc($1)
|
||||
, 'upper_inc', upper_inc($1)
|
||||
);
|
||||
$$ language sql;
|
||||
|
||||
Then, create the cast using this function:
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create cast (tsrange as json) with function tsrange_to_json(tsrange) as assignment;
|
||||
|
||||
Finally, do the request :ref:`casting the range column <casting_columns>`:
|
||||
|
||||
.. tabs::
|
||||
|
||||
.. code-tab:: http
|
||||
|
||||
GET /events?select=id,name,duration::json HTTP/1.1
|
||||
|
||||
.. code-tab:: bash Curl
|
||||
|
||||
curl "http://localhost:3000/events?select=id,name,duration::json"
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "New Year's Party",
|
||||
"duration": {
|
||||
"lower": "2022-12-31T11:00:00",
|
||||
"upper": "2023-01-01T06:00:00",
|
||||
"lower_inc": true,
|
||||
"upper_inc": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
.. note::
|
||||
|
||||
If you don't want to modify casts for built-in types, an option would be to `create a custom type <https://www.postgresql.org/docs/current/sql-createtype.html>`_
|
||||
for your own ``tsrange`` and add its own cast.
|
||||
|
||||
.. code-block:: postgres
|
||||
|
||||
create type mytsrange as range (subtype = timestamp, subtype_diff = tsrange_subdiff);
|
||||
|
||||
-- define column types and casting function analogously to the above example
|
||||
-- ...
|
||||
|
||||
create cast (mytsrange as json) with function mytsrange_to_json(mytsrange) as assignment;
|
||||
|
||||
hstore
|
||||
------
|
||||
|
||||
|
||||
@@ -204,10 +204,7 @@ These are recipes that'll help you address specific use-cases.
|
||||
|
||||
how-tos/*
|
||||
|
||||
- :doc:`how-tos/casting-type-to-custom-json`
|
||||
- :doc:`how-tos/embedding-table-from-another-schema`
|
||||
- :doc:`how-tos/providing-images-for-img`
|
||||
- `How PostgreSQL triggers work when called with a PostgREST PATCH HTTP request <https://blog.fgribreau.com/2020/11/how-postgresql-triggers-works-when.html>`_
|
||||
- :doc:`how-tos/working-with-postgresql-data-types`
|
||||
|
||||
Ecosystem
|
||||
|
||||
Reference in New Issue
Block a user