Files
postgrest/docs/api/writing.md
T

211 lines
6.0 KiB
Markdown

## Updating Data
### Record Creation
* ❌ Cannot be cached or prefetched
* ❌ Not idempotent
To create a row in a database table post a JSON object whose keys
are the names of the columns you would like to create. Missing keys
will be set to default values when applicable.
```HTTP
POST /table_name
{ "col1": "value1", "col2": "value2" }
```
The response will include a `Location` header describing where to
find the new object. If you would like to get the full object back
in the response to your request, include the header `Prefer:
return=representation`. That way you won't have to make another
HTTP call to discover properties that may have been filled in on
the server side.
### Bulk Insertion
* ❌ Cannot be cached or prefetched
* ❌ Not idempotent
You can POST a JSON array or CSV to insert multiple rows in a single
HTTP request. Note that using CSV requires less parsing on the server
and is **much faster**.
Example of CSV bulk insert. Simply post to a table route with
`Content-Type: text/csv` and include the names of the columns as
the first row. For instance
```HTTP
POST /people
name,age,height
J Doe,62,70
Jonas,10,55
```
An empty field (`,,`) is coerced to an empty string and the reserved
word `NULL` is mapped to the SQL null value. Note that there should
be no spaces between the column names and commas.
Example of JSON bulk insert. Send an array:
```HTTP
POST /people
[
{ "name": "J Doe", "age": 62, "height": 70 },
{ "name": "Janus", "age": 10, "height": 55 }
]
```
If you would like to get the full object back in the response to
your request, include the header `Prefer: return=representation`.
Chances are you only want certain information back, though, like
created ids. You can pass a `select` parameter to affect the shape
of the response (further documented in the [reading](/api/reading/)
page). For instance
```HTTP
POST /people?select=id
[...]
```
returns something like
```json
[ { "id": 1 }, { "id": 2 } ]
```
### Multiple Tables Insertion or Update
The cleanest way to insert or update data into multiple tables using only one POST/PATCH request
is to create a view that will join all target tables and present a single endpoint.
In our example let's assume one users table and one companies table.
In this case, we want a signup endpoint to create the first user within a company.
And for this endpoint we want to insert with one request both user and company.
```SQL
CREATE TABLE companies (
id serial primary key,
name text unique
);
CREATE TABLE users (
id serial primary key,
name text not null,
pass text,
company_id integer not null references companies
);
```
Having both tables created we create a view that joins them to be used
as a ```/signup``` endpoint.
```SQL
CREATE VIEW signup AS
SELECT
c.name AS company_name,
u.name AS user_name,
u.pass
FROM
public.users u
JOIN public.companies c ON c.id = u.company_id;
```
After the signup view creation, we can issue ```GET``` requests to read data
from users and companies, but any atempt to ```POST``` or ```PATCH``` data will fail.
PostgreSQL won't allow any data change on views that have a ```JOIN```
clause in their ```FROM``` without a proper ```INSTEAD OF``` trigger.
So in the example bellow we create a trigger to allow insertion of data in the signup view.
The trigger is a simple PL/pgSQL function that first inserts into the companies table and
uses the newly create company_id to create its first user.
```SQL
CREATE FUNCTION signup()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
vcompany_id int;
BEGIN
INSERT INTO companies (name) VALUES (new.company_name) RETURNING id INTO vcompany_id;
INSERT INTO users (name, pass, company_id) VALUES (new.user_name, new.pass, vcompany_id);
RETURN new;
END;
$$;
CREATE TRIGGER signup
INSTEAD OF INSERT ON signup
FOR EACH ROW
EXECUTE PROCEDURE signup();
```
After the trigger creation we can issue a normal ```POST``` request to our signup endpoint:
```HTTP
POST /signup
{ "company_name": "foo", "user_name": "bar" }
```
For an endpoint such as signup its usually not desirable to have a ```PATCH``` route for updates,
and we will skip this example for the sake of brevity. But it would be implemented in a very
similar way to our ```POST``` example.
<div class="admonition note">
<p class="admonition-title">Design Consideration</p>
<p>It's advisable to create a separate trigger for <code>UPDATE</code> and <code>INSERT</code>
avoiding conditionals that decide which is the trigger current operation.
This makes it easier to change code for (or even disable) one operation without intefering with others while
improving readability.
</p>
</div>
### Bulk Updates
* ❌ Cannot be cached or prefetched
* ❌ Not idempotent
To change parts of a resource or resources use the `PATCH` verb.
For instance, here is how to mark all young people as children.
```HTTP
PATCH /people?age=lt.13
{
"person_type": "child"
}
```
This affects any rows matched by the url param filters, overwrites
any fields specified in in the payload JSON and leaves the other
fields unaffected. Note that although the payload is not in the
JSON patch format specified by
[RFC6902](https://tools.ietf.org/html/rfc6902), HTTP does not specify
which patch format to use. Our format is more pleasant, meant for
basic field replacements, and not at all "incorrect."
### Deletion
* ❌ Cannot be cached or prefetched
* ✅ Idempotent
Simply use the `DELETE` verb. All recors that match your filter
will be removed. For instance deleting inactive users:
```HTTP
DELETE /user?active=is.false
```
### Protecting Dangerous Actions
Notice that it is very easy to delete or update many records at
once. In fact forgetting a filter will affect an entire table!
<div class="admonition warning">
<p class="admonition-title">Invitation to Contribute</p>
<p>We would like to investigate nginx rules to guard dangerous
actions, perhaps requiring a confirmation header or query param
to perform the action.</p>
<p>You're invited to research this option and contribute to
this documentation.</p>
</div>