chore: Move *.rst files to docs/ folder

Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
This commit is contained in:
Wolfgang Walther
2022-01-30 13:35:55 +01:00
committed by Wolfgang Walther
parent 0ae86b7e74
commit a85dfe9558
45 changed files with 17 additions and 11 deletions
@@ -0,0 +1,97 @@
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;
@@ -0,0 +1,81 @@
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"}]}
]
+128
View File
@@ -0,0 +1,128 @@
.. _providing_img:
Providing images for ``<img>``
==============================
:author: `pkel <https://github.com/pkel>`_
In this how-to, you will learn how to create an endpoint for providing images to HTML :code:`<img>` tags without client side JavaScript.
The resulting HTML might look like this:
.. code-block:: html
<img src="http://host/files/42/cats.jpeg" alt="Cute Kittens"/>
In fact, the presented technique is suitable for providing not only images, but arbitrary files.
We will start with a minimal example that highlights the general concept.
Afterwards we present are more detailed solution that fixes a few shortcomings of the first approach.
Minimal Example
---------------
PostgREST returns binary data on requests that set the :code:`Accept: application/octet-stream` header.
The general idea is to configure the reverse proxy in front of the API to set this header for all requests to :code:`/files/`.
We will show how to achieve this using Nginx.
First, we need a public table for storing the files.
.. code-block:: postgres
create table files(
id int primary key
, blob bytea
);
Let's assume this table contains an image of two cute kittens with id 42.
We can retrieve this image in binary format from our PostgREST API by requesting :code:`/files?select=blob&id=eq.42` with the :code:`Accept: application/octet-stream` header.
Unfortunately, putting the URL into the :code:`src` of an :code:`<img>` tag will not work.
That's because browsers do not send the required header.
Luckily, we can configure our :doc:`Nginx reverse proxy <../admin>` to fix this problem for us.
We assume that PostgREST is running on port 3000.
We provide a new location :code:`/files/` that redirects requests to our endpoint with the :code:`Accept` header set to :code:`application/octet-stream`.
.. code-block:: nginx
server {
# rest of reverse proxy and web server configuration
...
location /files/ {
# /files/<id>/* ---> /files?select=blob&id=eq.<id>
rewrite /files/([^/]+).* /files?select=blob&id=eq.$1 break;
# if id is missing
return 404;
# request binary output
proxy_set_header Accept application/octet-stream;
# usual proxy setup
proxy_hide_header Content-Location;
add_header Content-Location /api/$upstream_http_content_location;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_pass http://localhost:3000/;
}
With this setup, we can request the cat image at :code:`localhost/files/42/cats.jpeg` without setting any headers.
In fact, you can replace :code:`cats.jpeg` with any other filename or simply omit it.
Putting the URL into the :code:`src` of an :code:`<img>` tag should now work as expected.
Improved Version
----------------
The basic solution has some shortcomings:
1. The response :code:`Content-Type` header is set to :code:`application/octet-stream`.
This might confuse clients and users.
2. Download requests (e.g. Right Click -> Save Image As) to :code:`files/42` will propose :code:`42` as filename.
This might confuse users.
3. Requests to the binary endpoint are not cached.
This will cause unnecessary load on the database.
The following improved version addresses these problems.
First, we store the media types and names of our files in the database.
.. code-block:: postgres
create table files(
id int primary key
, type text
, name text
, blob bytea
);
Next, we set up an RPC endpoint that sets the content type and filename.
We use this opportunity to configure some basic, client-side caching.
For production, you probably want to configure additional caches, e.g. on the reverse proxy.
.. code-block:: postgres
create function file(id int) returns bytea as
$$
declare headers text;
declare blob bytea;
begin
select format(
'[{"Content-Type": "%s"},'
'{"Content-Disposition": "inline; filename=\"%s\""},'
'{"Cache-Control": "max-age=259200"}]'
, files.type, files.name)
from files where files.id = file.id into headers;
perform set_config('response.headers', headers, true);
select files.blob from files where files.id = file.id into blob;
if found
then return(blob);
else raise sqlstate 'PT404' using
message = 'NOT FOUND',
detail = 'File not found',
hint = format('%s seems to be an invalid file id', file.id);
end if;
end
$$ language plpgsql;
With this, we can obtain the cat image from :code:`/rpc/file?id=42`.
Consequently, we have to replace our previous rewrite rule in the Nginx recipe with the following.
.. code-block:: nginx
rewrite /files/([^/]+).* /rpc/file?id=$1 break;