diff --git a/admin.rst b/admin.rst index df318e516..d1352228a 100644 --- a/admin.rst +++ b/admin.rst @@ -43,15 +43,27 @@ Block Full-Table Operations Each table in the admin-selected schema gets exposed as a top level route. Client requests are executed by certain database roles depending on their authentication. All HTTP verbs are supported that correspond to actions permitted to the role. For instance if the active role can drop rows of the table then the DELETE verb is allowed for clients. Here's an API request to delete old rows from a hypothetical logs table: -.. code-block:: http +.. tabs:: - DELETE /logs?time=lt.1991-08-06 HTTP/1.1 + .. code-tab:: http + + DELETE /logs?time=lt.1991-08-06 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/logs?time=lt.1991-08-06" -X DELETE However it's very easy to delete the **entire table** by omitting the query parameter! -.. code-block:: http +.. tabs:: - DELETE /logs HTTP/1.1 + .. code-tab:: http + + DELETE /logs HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/logs" -X DELETE This can happen accidentally such as by switching a request from a GET to a DELETE. To protect against accidental operations use the `pg-safeupdate `_ PostgreSQL extension. It raises an error if UPDATE or DELETE are executed without specifying conditions. To install it you can use the `PGXN `_ network: @@ -293,10 +305,17 @@ Alternate URL Structure As discussed in :ref:`singular_plural`, there are no special URL forms for singular resources in PostgREST, only operators for filtering. Thus there are no URLs like :code:`/people/1`. It would be specified instead as -.. code:: http +.. tabs:: - GET /people?id=eq.1 HTTP/1.1 - Accept: application/vnd.pgrst.object+json + .. code-tab:: http + + GET /people?id=eq.1 HTTP/1.1 + Accept: application/vnd.pgrst.object+json + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?id=eq.1" \ + -H "Accept: application/vnd.pgrst.object+json" This allows compound primary keys and makes the intent for singular response independent of a URL convention. diff --git a/api.rst b/api.rst index e85864d60..5e3fbe1ec 100644 --- a/api.rst +++ b/api.rst @@ -6,9 +6,15 @@ Tables and Views All views and tables in the exposed schema and accessible by the active database role for a request are available for querying. They are exposed in one-level deep routes. For instance the full contents of a table `people` is returned at -.. code-block:: http +.. tabs:: - GET /people HTTP/1.1 + .. code-tab:: http + + GET /people HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people" There are no deeply/nested/routes. Each route provides OPTIONS, GET, HEAD, POST, PATCH, and DELETE verbs depending entirely on database permissions. @@ -23,27 +29,51 @@ Horizontal Filtering (Rows) You can filter result rows by adding conditions on columns, each condition a query string parameter. For instance, to return people aged under 13 years old: -.. code-block:: http +.. tabs:: - GET /people?age=lt.13 HTTP/1.1 + .. code-tab:: http + + GET /people?age=lt.13 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?age=lt.13" Multiple parameters can be logically conjoined by: -.. code-block:: http +.. tabs:: - GET /people?age=gte.18&student=is.true HTTP/1.1 + .. code-tab:: http + + GET /people?age=gte.18&student=is.true HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?age=gte.18&student=is.true" Multiple parameters can be logically disjoined by: -.. code-block:: http +.. tabs:: - GET /people?or=(age.gte.14,age.lte.18) HTTP/1.1 + .. code-tab:: http + + GET /people?or=(age.gte.14,age.lte.18) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?or=(age.gte.14,age.lte.18)" Complex logic can also be applied: -.. code-block:: http +.. tabs:: - GET /people?and=(grade.gte.90,student.is.true,or(age.gte.14,age.is.null)) HTTP/1.1 + .. code-tab:: http + + GET /people?and=(grade.gte.90,student.is.true,or(age.gte.14,age.is.null)) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?and=(grade.gte.90,student.is.true,or(age.gte.14,age.is.null))" .. _operators: @@ -99,9 +129,15 @@ For more complicated filters you will have to create a new view in the database, The view will provide a new endpoint: -.. code-block:: http +.. tabs:: - GET /fresh_stories HTTP/1.1 + .. code-tab:: http + + GET /fresh_stories HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/fresh_stories" .. _fts: @@ -110,21 +146,45 @@ Full-Text Search The :code:`fts` filter mentioned above has a number of options to support flexible textual queries, namely the choice of plain vs phrase search and the language used for stemming. Suppose that :code:`tsearch` is a table with column :code:`my_tsv`, of type `tsvector `_. The following examples illustrate the possibilities. -.. code-block:: http +.. tabs:: - GET /tsearch?my_tsv=fts(french).amusant HTTP/1.1 + .. code-tab:: http -.. code-block:: http + GET /tsearch?my_tsv=fts(french).amusant HTTP/1.1 - GET /tsearch?my_tsv=plfts.The%20Fat%20Cats HTTP/1.1 + .. code-tab:: bash Curl -.. code-block:: http + curl "http://localhost:3000/tsearch?my_tsv=fts(french).amusant" - GET /tsearch?my_tsv=not.phfts(english).The%20Fat%20Cats HTTP/1.1 +.. tabs:: -.. code-block:: http + .. code-tab:: http - GET /tsearch?my_tsv=not.wfts(french).amusant HTTP/1.1 + GET /tsearch?my_tsv=plfts.The%20Fat%20Cats HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/tsearch?my_tsv=plfts.The%20Fat%20Cats" + +.. tabs:: + + .. code-tab:: http + + GET /tsearch?my_tsv=not.phfts(english).The%20Fat%20Cats HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/tsearch?my_tsv=not.phfts(english).The%20Fat%20Cats" + +.. tabs:: + + .. code-tab:: http + + GET /tsearch?my_tsv=not.wfts(french).amusant HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/tsearch?my_tsv=not.wfts(french).amusant" Using phrase search mode requires PostgreSQL of version at least 9.6 and will raise an error in earlier versions of the database. @@ -137,9 +197,17 @@ Vertical Filtering (Columns) When certain columns are wide (such as those holding binary data), it is more efficient for the server to withhold them in a response. The client can specify which columns are required using the :sql:`select` parameter. -.. code-block:: http +.. tabs:: - GET /people?select=first_name,age HTTP/1.1 + .. code-tab:: http + + GET /people?select=first_name,age HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=first_name,age" + +.. code-block:: json [ {"first_name": "John", "age": 30}, @@ -153,9 +221,17 @@ Renaming Columns You can rename the columns by prefixing them with an alias followed by the colon ``:`` operator. -.. code-block:: http +.. tabs:: - GET /people?select=fullName:full_name,birthDate:birth_date HTTP/1.1 + .. code-tab:: http + + GET /people?select=fullName:full_name,birthDate:birth_date HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=fullName:full_name,birthDate:birth_date" + +.. code-block:: json [ {"fullName": "John Doe", "birthDate": "04/25/1988"}, @@ -169,9 +245,17 @@ Casting Columns Casting the columns is possible by suffixing them with the double colon ``::`` plus the desired type. -.. code-block:: http +.. tabs:: - GET /people?select=full_name,salary::text HTTP/1.1 + .. code-tab:: http + + GET /people?select=full_name,salary::text HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=full_name,salary::text" + +.. code-block:: json [ {"full_name": "John Doe", "salary": "90000.00"}, @@ -185,18 +269,34 @@ JSON Columns You can specify a path for a ``json`` or ``jsonb`` column using the arrow operators(``->`` or ``->>``) as per the `PostgreSQL docs `_. -.. code-block:: http +.. tabs:: - GET /people?select=id,json_data->>blood_type,json_data->phones HTTP/1.1 + .. code-tab:: http + + GET /people?select=id,json_data->>blood_type,json_data->phones HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=id,json_data->>blood_type,json_data->phones" + +.. code-block:: json [ { "id": 1, "blood_type": "A-", "phones": [{"country_code": "61", "number": "917-929-5745"}] }, { "id": 2, "blood_type": "O+", "phones": [{"country_code": "43", "number": "512-446-4988"}, {"country_code": "43", "number": "213-891-5979"}] } ] -.. code-block:: http +.. tabs:: - GET /people?select=id,json_data->phones->0->>number HTTP/1.1 + .. code-tab:: http + + GET /people?select=id,json_data->phones->0->>number HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=id,json_data->phones->0->>number" + +.. code-block:: json [ { "id": 1, "number": "917-929-5745"}, @@ -205,9 +305,17 @@ You can specify a path for a ``json`` or ``jsonb`` column using the arrow operat This also works with filters: -.. code-block:: http +.. tabs:: - GET /people?select=id,json_data->blood_type&json_data->>blood_type=eq.A- HTTP/1.1 + .. code-tab:: http + + GET /people?select=id,json_data->blood_type&json_data->>blood_type=eq.A- HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=id,json_data->blood_type&json_data->>blood_type=eq.A-" + +.. code-block:: json [ { "id": 1, "blood_type": "A-" }, @@ -217,9 +325,17 @@ This also works with filters: Note that ``->>`` is used to compare ``blood_type`` as ``text``. To compare with an integer value use ``->``: -.. code-block:: http +.. tabs:: - GET /people?select=id,json_data->age&json_data->age=gt.20 HTTP/1.1 + .. code-tab:: http + + GET /people?select=id,json_data->age&json_data->age=gt.20 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=id,json_data->age&json_data->age=gt.20" + +.. code-block:: json [ { "id": 11, "age": 25 }, @@ -251,15 +367,27 @@ Filters may be applied to computed columns(**a.k.a. virtual columns**) as well a A full-text search on the computed column: -.. code-block:: http +.. tabs:: - GET /people?full_name=fts.Beckett HTTP/1.1 + .. code-tab:: http + + GET /people?full_name=fts.Beckett HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?full_name=fts.Beckett" As mentioned, computed columns do not appear in the output by default. However you can include them by listing them in the vertical filtering :code:`select` parameter: -.. code-block:: HTTP +.. tabs:: - GET /people?select=*,full_name HTTP/1.1 + .. code-tab:: http + + GET /people?select=*,full_name HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?select=*,full_name" .. important:: @@ -278,9 +406,15 @@ To request this: Do this: -.. code-block:: http +.. tabs:: - GET /%D9%85%D9%88%D8%A7%D8%B1%D8%AF HTTP/1.1 + .. code-tab:: http + + GET /%D9%85%D9%88%D8%A7%D8%B1%D8%AF HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/%D9%85%D9%88%D8%A7%D8%B1%D8%AF" .. _tabs-cols-w-spaces: @@ -289,9 +423,15 @@ Table / Columns with spaces You can request table/columns with spaces in them by percent encoding the spaces with ``%20``: -.. code-block:: http +.. tabs:: - GET /Order%20Items?Unit%20Price=lt.200 HTTP/1.1 + .. code-tab:: http + + GET /Order%20Items?Unit%20Price=lt.200 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/Order%20Items?Unit%20Price=lt.200" .. _reserved-chars: @@ -302,15 +442,27 @@ If filters include PostgREST reserved characters(``,``, ``.``, ``:``, ``()``) yo Here ``Hebdon,John`` and ``Williams,Mary`` are values. -.. code-block:: http +.. tabs:: - GET /employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22) HTTP/1.1 + .. code-tab:: http + + GET /employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/employees?name=in.(%22Hebdon,John%22,%22Williams,Mary%22)" Here ``information.cpe`` is a column name. -.. code-block:: http +.. tabs:: - GET /vulnerabilities?%22information.cpe%22=like.*MS* HTTP/1.1 + .. code-tab:: http + + GET /vulnerabilities?%22information.cpe%22=like.*MS* HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/vulnerabilities?%22information.cpe%22=like.*MS*" If the value filtered by the ``in`` operator has a double quote (``"``), you can escape it using a backslash ``"\""``. A backslash itself can be used with a double backslash ``"\\"``. @@ -330,25 +482,49 @@ Ordering The reserved word :sql:`order` reorders the response rows. It uses a comma-separated list of columns and directions: -.. code-block:: http +.. tabs:: - GET /people?order=age.desc,height.asc HTTP/1.1 + .. code-tab:: http + + GET /people?order=age.desc,height.asc HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?order=age.desc,height.asc" If no direction is specified it defaults to ascending order: -.. code-block:: http +.. tabs:: - GET /people?order=age HTTP/1.1 + .. code-tab:: http + + GET /people?order=age HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?order=age" If you care where nulls are sorted, add ``nullsfirst`` or ``nullslast``: -.. code-block:: http +.. tabs:: - GET /people?order=age.nullsfirst HTTP/1.1 + .. code-tab:: http -.. code-block:: http + GET /people?order=age.nullsfirst HTTP/1.1 - GET /people?order=age.desc.nullslast HTTP/1.1 + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?order=age.nullsfirst" + +.. tabs:: + + .. code-tab:: http + + GET /people?order=age.desc.nullslast HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?order=age.desc.nullslast" You can also use :ref:`computed_cols` to order the results, even though the computed columns will not appear in the output. @@ -369,11 +545,19 @@ Here items zero through fourteen are returned. This information is available in There are two ways to apply a limit and offset rows: through request headers or query parameters. When using headers you specify the range of rows desired. This request gets the first twenty people. -.. code-block:: http +.. tabs:: - GET /people HTTP/1.1 - Range-Unit: items - Range: 0-19 + .. code-tab:: http + + GET /people HTTP/1.1 + Range-Unit: items + Range: 0-19 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people" -i \ + -H "Range-Unit: items" \ + -H "Range: 0-19" Note that the server may respond with fewer if unable to meet your request: @@ -387,9 +571,15 @@ You may also request open-ended ranges for an offset with no limit, e.g. :code:` The other way to request a limit or offset is with query parameters. For example -.. code-block:: http +.. tabs:: - GET /people?limit=15&offset=30 HTTP/1.1 + .. code-tab:: http + + GET /people?limit=15&offset=30 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people?limit=15&offset=30" This method is also useful for embedded resources, which we will cover in another section. The server always responds with range headers even if you use query parameters to limit the query. @@ -400,12 +590,21 @@ Exact Count In order to obtain the total size of the table or view (such as when rendering the last page link in a pagination control), specify ``Prefer: count=exact`` as a request header: -.. code-block:: http +.. tabs:: - HEAD /bigtable HTTP/1.1 - Range-Unit: items - Range: 0-24 - Prefer: count=exact + .. code-tab:: http + + HEAD /bigtable HTTP/1.1 + Range-Unit: items + Range: 0-24 + Prefer: count=exact + + .. code-tab:: bash Curl + + curl "http://localhost:3000/bigtable" -I \ + -H "Range-Unit: items" \ + -H "Range: 0-24" \ + -H "Prefer: count=exact" Note that the larger the table the slower this query runs in the database. The server will respond with the selected range and total @@ -423,10 +622,17 @@ Planned Count To avoid the shortcomings of :ref:`exact count `, PostgREST can leverage PostgreSQL statistics and get a fairly accurate and fast count. To do this, specify the ``Prefer: count=planned`` header. -.. code-block:: http +.. tabs:: - HEAD /bigtable?limit=25 HTTP/1.1 - Prefer: count=planned + .. code-tab:: http + + HEAD /bigtable?limit=25 HTTP/1.1 + Prefer: count=planned + + .. code-tab:: bash Curl + + curl "http://localhost:3000/bigtable?limit=25" -I \ + -H "Prefer: count=planned" .. code-block:: http @@ -453,10 +659,17 @@ defined by :ref:`max-rows`. Here's an example. Suppose we set ``max-rows=1000`` and ``smalltable`` has 321 rows, then we'll get the exact count: -.. code-block:: http +.. tabs:: - HEAD /smalltable?limit=25 HTTP/1.1 - Prefer: count=estimated + .. code-tab:: http + + HEAD /smalltable?limit=25 HTTP/1.1 + Prefer: count=estimated + + .. code-tab:: bash Curl + + curl "http://localhost:3000/smalltable?limit=25" -I \ + -H "Prefer: count=estimated" .. code-block:: http @@ -465,10 +678,17 @@ Here's an example. Suppose we set ``max-rows=1000`` and ``smalltable`` has 321 r If we make a similar request on ``bigtable``, which has 3573458 rows, we would get the planned count: -.. code-block:: http +.. tabs:: - HEAD /bigtable?limit=25 HTTP/1.1 - Prefer: count=estimated + .. code-tab:: http + + HEAD /bigtable?limit=25 HTTP/1.1 + Prefer: count=estimated + + .. code-tab:: bash Curl + + curl "http://localhost:3000/bigtable?limit=25" -I \ + -H "Prefer: count=estimated" .. code-block:: http @@ -484,10 +704,17 @@ PostgREST uses proper HTTP content negotiation (`RFC7231 `_. @@ -848,9 +1166,15 @@ Here's a sample function (notice the ``RETURNS SETOF films``). A request with ``directors`` embedded: -.. code-block:: http +.. tabs:: - GET /rpc/getallfilms?select=title,directors(id,last_name)&title=like.*Workers* HTTP/1.1 + .. code-tab:: http + + GET /rpc/getallfilms?select=title,directors(id,last_name)&title=like.*Workers* HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/getallfilms?select=title,directors(id,last_name)&title=like.*Workers*" .. code-block:: json @@ -872,19 +1196,36 @@ You can embed related resources after doing :ref:`insert_update` or :ref:`delete Say you want to insert a **film** and then get some of its attributes plus embed its **director**. -.. code-block:: http +.. tabs:: - POST /films?select=title,year,director:directors(first_name,last_name) HTTP/1.1 - Prefer: return=representation + .. code-tab:: http - { - "id": 100, - "director_id": 40, - "title": "127 hours", - "year": 2010, - "rating": 7.6, - "language": "english" - } + POST /films?select=title,year,director:directors(first_name,last_name) HTTP/1.1 + Prefer: return=representation + + { + "id": 100, + "director_id": 40, + "title": "127 hours", + "year": 2010, + "rating": 7.6, + "language": "english" + } + + .. code-tab:: bash Curl + + curl "http://localhost:3000/films?select=title,year,director:directors(first_name,last_name)" \ + -H "Prefer: return=representation" \ + -d @- << EOF + { + "id": 100, + "director_id": 40, + "title": "127 hours", + "year": 2010, + "rating": 7.6, + "language": "english" + } + EOF Response: @@ -917,9 +1258,15 @@ For example, suppose you have the following ``orders`` and ``addresses`` tables: And you try to embed ``orders`` with ``addresses`` (this is the **target**): -.. code-block:: http +.. tabs:: - GET /orders?select=*,addresses(*) HTTP/1.1 + .. code-tab:: http + + GET /orders?select=*,addresses(*) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/orders?select=*,addresses(*)" -i Since the ``orders`` table has two foreign keys to the ``addresses`` table — an order has a billing address and a shipping address — the request is ambiguous and PostgREST will respond with an error: @@ -946,9 +1293,17 @@ Let's try first with the **foreign key constraint name**. To make it clearer we Now we can unambiguously embed the billing address by specifying the ``billing_address`` foreign key constraint as the **target**. -.. code-block:: http +.. tabs:: - GET /orders?select=name,billing_address(name) HTTP/1.1 + .. code-tab:: http + + GET /orders?select=name,billing_address(name) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/orders?select=name,billing_address(name)" + +.. code-block:: json [ { @@ -962,9 +1317,17 @@ Now we can unambiguously embed the billing address by specifying the ``billing_a Alternatively, you can specify the **column name** of the foreign key constraint as the **target**. This can be aliased to make the result more clear. -.. code-block:: http +.. tabs:: - GET /orders?select=name,billing_address:billing_address_id(name) HTTP/1.1 + .. code-tab:: http + + GET /orders?select=name,billing_address:billing_address_id(name) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/orders?select=name,billing_address:billing_address_id(name)" + +.. code-block:: json [ { @@ -984,18 +1347,34 @@ two views of ``addresses``: ``central_addresses`` and ``eastern_addresses``. Since PostgREST supports :ref:`embedding_views` by detecting **source foreign keys** in the views, embedding with the foreign key as the **target** will not be enough for an unambiguous embed: -.. code-block:: http +.. tabs:: - GET /orders?select=*,billing_address(*) HTTP/1.1 + .. code-tab:: http + + GET /orders?select=*,billing_address(*) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/orders?select=*,billing_address(*)" -i + +.. code-block:: http HTTP/1.1 300 Multiple Choices For solving this case, in addition to the **target**, we can add a **hint**. Here we specify ``central_addresses`` as the **target** and the ``billing_address`` foreign key as the **hint**: -.. code-block:: http +.. tabs:: - GET /orders?select=*,central_addresses!billing_address(*) HTTP/1.1 + .. code-tab:: http + + GET /orders?select=*,central_addresses!billing_address(*) HTTP/1.1 + + .. code-tab:: bash Curl + + curl 'http://localhost:3000/orders?select=*,central_addresses!billing_address(*)' -i + +.. code-block:: http HTTP/1.1 200 OK @@ -1018,11 +1397,19 @@ All tables and `auto-updatable views `_. @@ -1155,27 +1616,50 @@ On Conflict By specifying the ``on_conflict`` query parameter, you can make UPSERT work on a column(s) that has a UNIQUE constraint. -.. code-block:: http +.. tabs:: - POST /employees?on_conflict=name HTTP/1.1 - Prefer: resolution=merge-duplicates + .. code-tab:: http - [ - { "name": "Old employee 1", "salary": 40000 }, - { "name": "Old employee 2", "salary": 52000 }, - { "name": "New employee 3", "salary": 60000 } - ] + POST /employees?on_conflict=name HTTP/1.1 + Prefer: resolution=merge-duplicates + + [ + { "name": "Old employee 1", "salary": 40000 }, + { "name": "Old employee 2", "salary": 52000 }, + { "name": "New employee 3", "salary": 60000 } + ] + + .. code-tab:: bash Curl + + curl "http://localhost:3000/employees?on_conflict=name" \ + -X POST -H "Content-Type: application/json" + -H "Prefer: resolution=merge-duplicates" \ + -d @- << EOF + [ + { "name": "Old employee 1", "salary": 40000 }, + { "name": "Old employee 2", "salary": 52000 }, + { "name": "New employee 3", "salary": 60000 } + ] + EOF PUT ~~~ A single row UPSERT can be done by using :code:`PUT` and filtering the primary key columns with :code:`eq`: -.. code-block:: http +.. tabs:: - PUT /employees?id=eq.4 HTTP/1.1 + .. code-tab:: http - { "id": 4, "name": "Sara B.", "salary": 60000 } + PUT /employees?id=eq.4 HTTP/1.1 + + { "id": 4, "name": "Sara B.", "salary": 60000 } + + .. code-tab:: bash Curl + + curl "http://localhost/employees?id=eq.4" \ + -X PUT -H "Content-Type: application/json" \ + -d '{ "id": 4, "name": "Sara B.", "salary": 60000 }' All the columns must be specified in the request body, including the primary key columns. @@ -1190,9 +1674,15 @@ Deletions To delete rows in a table, use the DELETE verb plus :ref:`h_filter`. For instance deleting inactive users: -.. code-block:: http +.. tabs:: - DELETE /user?active=is.false HTTP/1.1 + .. code-tab:: http + + DELETE /user?active=is.false HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/user?active=is.false" -X DELETE Deletions also support :code:`Prefer: return=representation` plus :ref:`v_filter`. @@ -1225,9 +1715,15 @@ Stored Procedures Every stored procedure in the API-exposed database schema is accessible under the :code:`/rpc` prefix. The API endpoint supports POST (and in some cases GET) to execute the function. -.. code-block:: http +.. tabs:: - POST /rpc/function_name HTTP/1.1 + .. code-tab:: http + + POST /rpc/function_name HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/function_name" -X POST Such functions can perform any operations allowed by PostgreSQL (read data, modify data, and even DDL operations). @@ -1248,11 +1744,21 @@ For instance, assume we have created this function in the database. The client can call it by posting an object like -.. code-block:: http +.. tabs:: - POST /rpc/add_them HTTP/1.1 + .. code-tab:: http - { "a": 1, "b": 2 } + POST /rpc/add_them HTTP/1.1 + + { "a": 1, "b": 2 } + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/add_them" \ + -X POST -H "Content-Type: application/json" \ + -d '{ "a": 1, "b": 2 }' + +.. code-block:: json 3 @@ -1290,9 +1796,15 @@ Procedures that do not modify the database can be called with the HTTP GET verb Because ``add_them`` is ``IMMUTABLE``, we can alternately call the function with a GET request: -.. code-block:: http +.. tabs:: - GET /rpc/add_them?a=1&b=2 HTTP/1.1 + .. code-tab:: http + + GET /rpc/add_them?a=1&b=2 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/add_them?a=1&b=2" The function parameter names match the JSON object keys in the POST case, for the GET case they match the query parameters ``?a=1&b=2``. @@ -1307,12 +1819,23 @@ You can also call a function that takes a single parameter of type JSON by sendi SELECT (param->>'x')::int * (param->>'y')::int $$ LANGUAGE SQL; -.. code-block:: http +.. tabs:: - POST /rpc/mult_them HTTP/1.1 - Prefer: params=single-object + .. code-tab:: http - { "x": 4, "y": 2 } + POST /rpc/mult_them HTTP/1.1 + Prefer: params=single-object + + { "x": 4, "y": 2 } + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/mult_them" \ + -X POST -H "Content-Type: application/json" \ + -H "Prefer: params=single-object" \ + -d '{ "x": 4, "y": 2 }' + +.. code-block:: json 8 @@ -1329,12 +1852,20 @@ You can call a function that takes an array parameter: SELECT array_agg(n + 1) FROM unnest($1) AS n; $$ language sql; -.. code-block:: http +.. tabs:: - POST /rpc/plus_one HTTP/1.1 - Content-Type: application/json + .. code-tab:: http - {"arr": [1,2,3,4]} + POST /rpc/plus_one HTTP/1.1 + Content-Type: application/json + + {"arr": [1,2,3,4]} + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one" \ + -X POST -H "Content-Type: application/json" \ + -d '{"arr": [1,2,3,4]}' .. code-block:: json @@ -1343,19 +1874,33 @@ You can call a function that takes an array parameter: For calling the function with GET, you can pass the array as an `array literal `_, as in ``{1,2,3,4}``. Note that the curly brackets have to be urlencoded(``{`` is ``%7B`` and ``}`` is ``%7D``). -.. code-block:: http +.. tabs:: - GET /rpc/plus_one?arr=%7B1,2,3,4%7D' HTTP/1.1 + .. code-tab:: http + + GET /rpc/plus_one?arr=%7B1,2,3,4%7D' HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D'" .. note:: For versions prior to PostgreSQL 10, to pass a PostgreSQL native array on a POST payload, you need to quote it and use an array literal: - .. code-block:: http + .. tabs:: - POST /rpc/plus_one HTTP/1.1 + .. code-tab:: http - { "arr": "{1,2,3,4}" } + POST /rpc/plus_one HTTP/1.1 + + { "arr": "{1,2,3,4}" } + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one" \ + -X POST -H "Content-Type: application/json" \ + -d '{ "arr": "{1,2,3,4}" }' In these versions we recommend using function parameters of type JSON to accept arrays from the client. @@ -1372,12 +1917,20 @@ You can call a variadic function by passing a JSON array in a POST request: SELECT array_agg(n + 1) FROM unnest($1) AS n; $$ language sql; -.. code-block:: http +.. tabs:: - POST /rpc/plus_one HTTP/1.1 - Content-Type: application/json + .. code-tab:: http - {"v": [1,2,3,4]} + POST /rpc/plus_one HTTP/1.1 + Content-Type: application/json + + {"v": [1,2,3,4]} + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one" \ + -X POST -H "Content-Type: application/json" \ + -d '{"v": [1,2,3,4]}' .. code-block:: json @@ -1385,33 +1938,63 @@ You can call a variadic function by passing a JSON array in a POST request: In a GET request, you can repeat the same parameter name: -.. code-block:: http +.. tabs:: - GET /rpc/plus_one?v=1&v=2&v=3&v=4 HTTP/1.1 + .. code-tab:: http + + GET /rpc/plus_one?v=1&v=2&v=3&v=4 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4" Repeating also works in POST requests with ``Content-Type: application/x-www-form-urlencoded``: -.. code-block:: http +.. tabs:: - POST /rpc/plus_one HTTP/1.1 - Content-Type: application/x-www-form-urlencoded + .. code-tab:: http - v=1&v=2&v=3&v=4 + POST /rpc/plus_one HTTP/1.1 + Content-Type: application/x-www-form-urlencoded + + v=1&v=2&v=3&v=4 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one" \ + -X POST -H "Content-Type: application/x-www-form-urlencoded" + -d 'v=1&v=2&v=3&v=4' Scalar functions ---------------- PostgREST will detect if the function is scalar or table-valued and will shape the response format accordingly: -.. code-block:: http +.. tabs:: - GET /rpc/add_them?a=1&b=2 HTTP/1.1 + .. code-tab:: http + + GET /rpc/add_them?a=1&b=2 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/add_them?a=1&b=2" + +.. code-block:: json 3 -.. code-block:: http +.. tabs:: - GET /rpc/best_films_2017 HTTP/1.1 + .. code-tab:: http + + GET /rpc/best_films_2017 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/best_films_2017" + +.. code-block:: json [ { "title": "Okja", "rating": 7.4}, @@ -1427,15 +2010,28 @@ Bulk Call It's possible to call a function in a bulk way, analogously to :ref:`bulk_insert`. To do this, you need to add the ``Prefer: params=multiple-objects`` header to your request. -.. code-block:: http +.. tabs:: - POST /rpc/add_them HTTP/1.1 - Content-Type: text/csv - Prefer: params=multiple-objects + .. code-tab:: http - a,b - 1,2 - 3,4 + POST /rpc/add_them HTTP/1.1 + Content-Type: text/csv + Prefer: params=multiple-objects + + a,b + 1,2 + 3,4 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/add_them" \ + -X POST -H "Content-Type: text/csv" \ + -H "Prefer: params=multiple-objects" \ + --data-binary @- << EOF + a,b + 1,2 + 3,4 + EOF .. code-block:: json @@ -1454,13 +2050,25 @@ A function that returns a table type response can be shaped using the same filte CREATE FUNCTION best_films_2017() RETURNS SETOF films .. -.. code-block:: http +.. tabs:: - GET /rpc/best_films_2017?select=title,director:directors(*) HTTP/1.1 + .. code-tab:: http -.. code-block:: http + GET /rpc/best_films_2017?select=title,director:directors(*) HTTP/1.1 - GET /rpc/best_films_2017?rating=gt.8&order=title.desc HTTP/1.1 + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)" + +.. tabs:: + + .. code-tab:: http + + GET /rpc/best_films_2017?rating=gt.8&order=title.desc HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc" Overloaded functions -------------------- @@ -1473,13 +2081,25 @@ You can call overloaded functions with different number of arguments. CREATE FUNCTION rental_duration(customer_id integer, from_date date) .. -.. code-block:: http +.. tabs:: - GET /rpc/rental_duration?customer_id=232 HTTP/1.1 + .. code-tab:: http -.. code-block:: http + GET /rpc/rental_duration?customer_id=232 HTTP/1.1 - GET /rpc/rental_duration?customer_id=232&from_date=2018-07-01 HTTP/1.1 + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/rental_duration?customer_id=232" + +.. tabs:: + + .. code-tab:: http + + GET /rpc/rental_duration?customer_id=232&from_date=2018-07-01 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01" .. important:: @@ -1493,10 +2113,17 @@ Binary Output If you want to return raw binary data from a :code:`bytea` column, you must specify :code:`application/octet-stream` as part of the :code:`Accept` header and select a single column :code:`?select=bin_data`. -.. code-block:: http +.. tabs:: - GET /items?select=bin_data&id=eq.1 HTTP/1.1 - Accept: application/octet-stream + .. code-tab:: http + + GET /items?select=bin_data&id=eq.1 HTTP/1.1 + Accept: application/octet-stream + + .. code-tab:: bash Curl + + curl "http://localhost:3000/items?select=bin_data&id=eq.1" \ + -H "Accept: application/octet-stream" You can also request binary output when calling `Stored Procedures`_ and since they can return a scalar value you are not forced to use :code:`select` for this case. @@ -1505,10 +2132,17 @@ for this case. CREATE FUNCTION closest_point(..) RETURNS bytea .. -.. code-block:: http +.. tabs:: - POST /rpc/closest_point HTTP/1.1 - Accept: application/octet-stream + .. code-tab:: http + + POST /rpc/closest_point HTTP/1.1 + Accept: application/octet-stream + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/closest_point" \ + -X POST -H "Accept: application/octet-stream" If the stored procedure returns non-scalar values, you need to do a :code:`select` in the same way as for GET binary output. @@ -1516,10 +2150,17 @@ If the stored procedure returns non-scalar values, you need to do a :code:`selec CREATE FUNCTION overlapping_regions(..) RETURNS SETOF TABLE(geom_twkb bytea, ..) .. -.. code-block:: http +.. tabs:: - POST /rpc/overlapping_regions?select=geom_twkb HTTP/1.1 - Accept: application/octet-stream + .. code-tab:: http + + POST /rpc/overlapping_regions?select=geom_twkb HTTP/1.1 + Accept: application/octet-stream + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/overlapping_regions?select=geom_twkb" \ + -X POST -H "Accept: application/octet-stream" .. note:: @@ -1532,10 +2173,19 @@ Plain Text Output You can get raw output from a ``text`` column by using ``Accept: text/plain``. -.. code-block:: http +.. tabs:: - GET /workers?select=custom_psv_format HTTP/1.1 - Accept: text/plain + .. code-tab:: http + + GET /workers?select=custom_psv_format HTTP/1.1 + Accept: text/plain + + .. code-tab:: bash Curl + + curl "http://localhost:3000/workers?select=custom_psv_format" \ + -H "Accept: text/plain" + +.. code-block:: text 09310817|JOHN|DOE|15/04/88| 42152780|FRED|BLOGGS|20/02/85| @@ -1596,9 +2246,15 @@ You can verify which HTTP methods are allowed on endpoints for tables and views For a table named ``people``, OPTIONS would show: -.. code-block:: http +.. tabs:: - OPTIONS /people HTTP/1.1 + .. code-tab:: http + + OPTIONS /people HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people" -X OPTIONS -i .. code-block:: http @@ -1648,19 +2304,35 @@ You can switch schemas at runtime with the ``Accept-Profile`` and ``Content-Prof For GET or HEAD, the schema to be used can be selected through the ``Accept-Profile`` header: -.. code-block:: http +.. tabs:: - GET /items HTTP/1.1 - Accept-Profile: tenant2 + .. code-tab:: http + + GET /items HTTP/1.1 + Accept-Profile: tenant2 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/items" \ + -H "Accept-Profile: tenant2" For POST, PATCH, PUT and DELETE, you can use the ``Content-Profile`` header for selecting the schema: -.. code-block:: http +.. tabs:: - POST /items HTTP/1.1 - Content-Profile: tenant2 + .. code-tab:: http - {...} + POST /items HTTP/1.1 + Content-Profile: tenant2 + + {...} + + .. code-tab:: bash Curl + + curl "http://localhost:3000/items" \ + -X POST -H "Content-Type: application/json" \ + -H "Content-Profile: tenant2" \ + -d '{...}' You can also select the schema for :ref:`s_procs` and :ref:`open-api`. @@ -1763,17 +2435,24 @@ As an example, let's add some cache headers for all requests that come from an I Now when you make a GET request to a table or view, you'll get the cache headers. -.. code-block:: http +.. tabs:: - GET /people HTTP/1.1 - User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1) + .. code-tab:: http + + GET /people HTTP/1.1 + User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1) + + .. code-tab:: bash Curl + + curl "http://localhost:3000/people" -i \ + -H "User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)" + +.. code-block:: http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Cache-Control: no-cache, no-store, must-revalidate - ... - .. _guc_resp_status: Setting Response Status Code @@ -1791,9 +2470,15 @@ You can set the ``response.status`` GUC to override the default status code Post end; $$ language plpgsql; -.. code-block:: http +.. tabs:: - GET /rpc/teapot HTTP/1.1 + .. code-tab:: http + + GET /rpc/teapot HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/teapot" -i .. code-block:: http diff --git a/auth.rst b/auth.rst index 748a9fe29..8e3383cb0 100644 --- a/auth.rst +++ b/auth.rst @@ -150,10 +150,17 @@ Client Auth To make an authenticated request the client must include an :code:`Authorization` HTTP header with the value :code:`Bearer `. For instance: -.. code:: http +.. tabs:: - GET /foo HTTP/1.1 - Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4 + .. code-tab:: http + + GET /foo HTTP/1.1 + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/foo" \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiamRvZSIsImV4cCI6MTQ3NTUxNjI1MH0.GYDZV3yM0gqvuEtJmfpplLBXSGYnke_Pvnl0tbKAjB4" The ``Bearer`` header value can be used with or without capitalization(``bearer``). @@ -422,11 +429,19 @@ As described in `JWT from SQL`_, we'll create a JWT inside our login function. N An API request to call this function would look like: -.. code:: http +.. tabs:: - POST /rpc/login HTTP/1.1 + .. code-tab:: http - { "email": "foo@bar.com", "pass": "foobar" } + POST /rpc/login HTTP/1.1 + + { "email": "foo@bar.com", "pass": "foobar" } + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/login" \ + -X POST -H "Content-Type: application/json" \ + -d '{ "email": "foo@bar.com", "pass": "foobar" }' The response would look like the snippet below. Try decoding the token at `jwt.io `_. (It was encoded with a secret of :code:`reallyreallyreallyreallyverysafe` as specified in the SQL code above. You'll want to change this secret in your app!) diff --git a/conf.py b/conf.py index 6e017e016..be38720aa 100644 --- a/conf.py +++ b/conf.py @@ -28,7 +28,10 @@ import os # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = [ + 'sphinx_tabs.tabs', + 'sphinx_copybutton' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -293,3 +296,5 @@ def setup(app): # taken from https://github.com/sphinx-doc/sphinx/blob/82dad44e5bd3776ecb6fd8ded656bc8151d0e63d/sphinx/util/requests.py#L42 user_agent = 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0' +# sphinx-tabs configuration +sphinx_tabs_disable_tab_closing = True diff --git a/default.nix b/default.nix index 039ae825b..40db65825 100644 --- a/default.nix +++ b/default.nix @@ -15,7 +15,10 @@ let }) { }; - python = pkgs.python3.withPackages (ps: [ ps.sphinx ps.sphinx_rtd_theme ps.livereload ]); + sphinxTabsPkg = ps: ps.callPackage ./extensions/sphinx-tabs.nix {}; + sphinxCopybuttonPkg = ps: ps.callPackage ./extensions/sphinx-copybutton.nix {}; + + python = pkgs.python3.withPackages (ps: [ ps.sphinx ps.sphinx_rtd_theme ps.livereload (sphinxTabsPkg ps) (sphinxCopybuttonPkg ps) ]); in { inherit pkgs; diff --git a/extensions/sphinx-copybutton.nix b/extensions/sphinx-copybutton.nix new file mode 100644 index 000000000..8d408d300 --- /dev/null +++ b/extensions/sphinx-copybutton.nix @@ -0,0 +1,33 @@ +{ lib +, buildPythonPackage +, fetchFromGitHub +, sphinx +}: + +buildPythonPackage rec { + pname = "sphinx-copybutton"; + version = "0.4.0"; + + src = fetchFromGitHub { + owner = "executablebooks"; + repo = "sphinx-copybutton"; + rev = "v${version}"; + sha256 = "sha256-vrEIvQeP7AMXSme1PBp0ox5k8Q1rz+1cbHIO+o17Jqc="; + fetchSubmodules = true; + }; + + propagatedBuildInputs = [ + sphinx + ]; + + doCheck = false; # no tests + + pythonImportsCheck = [ "sphinx_copybutton" ]; + + meta = with lib; { + description = "A small sphinx extension to add a \"copy\" button to code blocks"; + homepage = "https://github.com/executablebooks/sphinx-copybutton"; + license = licenses.mit; + maintainers = with maintainers; [ Luflosi ]; + }; +} diff --git a/extensions/sphinx-tabs.nix b/extensions/sphinx-tabs.nix new file mode 100644 index 000000000..f90b1c18d --- /dev/null +++ b/extensions/sphinx-tabs.nix @@ -0,0 +1,29 @@ +{ lib +, buildPythonPackage +, fetchPypi +, sphinx +}: + +buildPythonPackage rec { + pname = "sphinx-tabs"; + version = "3.2.0"; + + src = fetchPypi { + inherit pname version; + sha256 = "sha256:1970aahi6sa7c37cpz8nwgdb2xzf21rk6ykdd1m6w9wvxla7j4rk"; + }; + + propagatedBuildInputs = [ + sphinx + ]; + + doCheck = false; + + pythonImportsCheck = [ "sphinx_tabs" ]; + + meta = with lib; { + description = "Create tabbed content in Sphinx documentation when building HTML"; + homepage = "https://sphinx-tabs.readthedocs.io"; + license = licenses.mit; + }; +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 163f583b5..7c1725324 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -docutils==0.17.1 \ No newline at end of file +docutils==0.17.1 +sphinx-tabs +sphinx-copybutton \ No newline at end of file diff --git a/schema_cache.rst b/schema_cache.rst index f6fd23749..1e708876d 100644 --- a/schema_cache.rst +++ b/schema_cache.rst @@ -41,9 +41,15 @@ Stale Foreign Key Relationships Suppose you add a ``cities`` table to your database and define a foreign key that references an existing ``countries`` table. Then, you make a request to get the ``cities`` and their belonging ``countries``. -.. code-block:: http +.. tabs:: - GET /cities?select=name,country:countries(id,name) HTTP/1.1 + .. code-tab:: http + + GET /cities?select=name,country:countries(id,name) HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/cities?select=name,country:countries(id,name)" The result will be an error: @@ -70,9 +76,15 @@ The same issue will occur on newly created functions on a running PostgREST. SELECT num + 1; $$ LANGUAGE SQL IMMUTABLE; -.. code-block:: http +.. tabs:: - GET /rpc/plus_one?num=1 HTTP/1.1 + .. code-tab:: http + + GET /rpc/plus_one?num=1 HTTP/1.1 + + .. code-tab:: bash Curl + + curl "http://localhost:3000/rpc/plus_one?num=1" .. code-block:: json