chore: Move *.rst files to docs/ folder
Signed-off-by: Wolfgang Walther <walther@technowledgy.de>
This commit is contained in:
committed by
Wolfgang Walther
parent
0ae86b7e74
commit
a85dfe9558
@@ -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"}]}
|
||||
]
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user