mirror of
https://github.com/supabase/supabase.git
synced 2026-06-23 18:46:40 +08:00
358 lines
8.7 KiB
Plaintext
358 lines
8.7 KiB
Plaintext
---
|
|
id: functions
|
|
title: Database Functions
|
|
description: Creating and using Postgres functions.
|
|
---
|
|
|
|
import Tabs from '@theme/Tabs'
|
|
import TabItem from '@theme/TabItem'
|
|
|
|
Postgres has built-in support for [SQL functions](https://www.postgresql.org/docs/current/sql-createfunction.html).
|
|
These functions live inside your database, and they can be [used with the API](../../reference/javascript/rpc).
|
|
|
|
## Quick demo
|
|
|
|
<div class="video-container">
|
|
<iframe
|
|
src="https://www.youtube-nocookie.com/embed/MJZCCpCYEqk"
|
|
frameBorder="1"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
></iframe>
|
|
</div>
|
|
|
|
## Getting started
|
|
|
|
Supabase provides several options for creating database functions. You can use the Dashboard or create them directly using SQL.
|
|
We provide a SQL editor within the Dashboard, or you can [connect](../../guides/database/connecting-to-postgres) to your database
|
|
and run the SQL queries yourself.
|
|
|
|
1. Go to the "SQL editor" section.
|
|
2. Click "New Query".
|
|
3. Enter the SQL to create or replace your Database function.
|
|
4. Click "Run" or cmd+enter (ctrl+enter).
|
|
|
|
## Simple Functions
|
|
|
|
Let's create a basic Database Function which returns a string "hello world".
|
|
|
|
```sql
|
|
create or replace function hello_world() -- 1
|
|
returns text -- 2
|
|
language sql -- 3
|
|
as $$ -- 4
|
|
select 'hello world'; -- 5
|
|
$$; --6
|
|
|
|
```
|
|
|
|
<details>
|
|
<summary>Show/Hide Details</summary>
|
|
|
|
At it's most basic a function has the following parts:
|
|
|
|
1. `create or replace function hello_world()`: The function declaration, where `hello_world` is the name of the function. You can use either `create` when creating a new function or `replace` when replacing an existing function. Or you can use `create or replace` together to handle either.
|
|
2. `returns text`: The type of data that the function returns. If it returns nothing, you can `returns void`.
|
|
3. `language sql`: The language used inside the function body. This can also be a procedural language: `plpgsql`, `plv8`, `plpython`, etc.
|
|
4. `as $$`: The function wrapper. Anything enclosed inside the `$$` symbols will be part of the function body.
|
|
5. `select 'hello world';`: A simple function body. The final `select` statement inside a function body will be returned if there are no statements following it.
|
|
6. `$$;`: The closing symbols of the function wrapper.
|
|
|
|
</details>
|
|
|
|
<br />
|
|
|
|
After the Function is created, we have several ways of "executing" the function - either directly inside the database using SQL, or with one of the client libraries.
|
|
|
|
<Tabs
|
|
defaultValue="SQL"
|
|
values={[
|
|
{label: 'SQL', value: 'SQL'},
|
|
{label: 'JS', value: 'JS'},
|
|
{label: 'Dart', value: 'Dart'},
|
|
]}>
|
|
|
|
<TabItem value="SQL">
|
|
|
|
```sql
|
|
select hello_world();
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="JS">
|
|
|
|
```js
|
|
const { data, error } = await supabase.rpc('hello_world')
|
|
```
|
|
|
|
Reference: [rpc()](../../reference/javascript/rpc)
|
|
|
|
</TabItem>
|
|
<TabItem value="Dart">
|
|
|
|
```dart
|
|
final res = await supabase
|
|
.rpc('hello_world')
|
|
.execute();
|
|
```
|
|
|
|
Reference: [rpc()](../../reference/dart/rpc)
|
|
|
|
</TabItem>
|
|
|
|
</Tabs>
|
|
|
|
## Returning data sets
|
|
|
|
Database Functions can also return data sets from [Tables](../../guides/database/tables) or Views.
|
|
|
|
For example, if we had a database with some Star Wars data inside:
|
|
|
|
<Tabs
|
|
defaultValue="Data"
|
|
values={[
|
|
{label: 'Data', value: 'Data'},
|
|
{label: 'SQL', value: 'SQL'},
|
|
]}>
|
|
|
|
<TabItem value="SQL">
|
|
|
|
```sql
|
|
create table planets (
|
|
id serial primary key,
|
|
name text
|
|
);
|
|
|
|
insert into planets (id, name)
|
|
values
|
|
(1, 'Tattoine'),
|
|
(2, 'Alderaan'),
|
|
(3, 'Kashyyyk');
|
|
|
|
create table people (
|
|
id serial primary key,
|
|
name text,
|
|
planet_id bigint references planets
|
|
);
|
|
|
|
insert into people (id, name, planet_id)
|
|
values
|
|
(1, 'Anakin Skywalker', 1),
|
|
(2, 'Luke Skywalker', 1),
|
|
(3, 'Princess Leia', 2),
|
|
(4, 'Chewbacca', 3);
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="Data">
|
|
|
|
<h4>Planets</h4>
|
|
|
|
| id | name |
|
|
| --- | -------- |
|
|
| 1 | Tattoine |
|
|
| 2 | Alderaan |
|
|
| 3 | Kashyyyk |
|
|
|
|
<h4>People</h4>
|
|
|
|
| id | name | planet_id |
|
|
| --- | ---------------- | --------- |
|
|
| 1 | Anakin Skywalker | 1 |
|
|
| 2 | Luke Skywalker | 1 |
|
|
| 3 | Princess Leia | 2 |
|
|
| 4 | Chewbacca | 3 |
|
|
|
|
</TabItem>
|
|
|
|
</Tabs>
|
|
|
|
We could create a function which returns all the planets:
|
|
|
|
```sql
|
|
create or replace function get_planets()
|
|
returns setof planets
|
|
language sql
|
|
as $$
|
|
select * from planets;
|
|
$$;
|
|
```
|
|
|
|
Because this function returns a table set, we can also apply filters and selectors. For example, if we only wanted the first planet:
|
|
|
|
<Tabs
|
|
defaultValue="SQL"
|
|
values={[
|
|
{label: 'SQL', value: 'SQL'},
|
|
{label: 'JS', value: 'JS'},
|
|
{label: 'Dart', value: 'Dart'},
|
|
]}>
|
|
|
|
<TabItem value="SQL">
|
|
|
|
```sql
|
|
select *
|
|
from get_planets()
|
|
where id = 1;
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="JS">
|
|
|
|
```js
|
|
const { data, error } = supabase.rpc('get_planets').eq('id', 1)
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="Dart">
|
|
|
|
```dart
|
|
final res = await supabase
|
|
.rpc('get_planets')
|
|
.eq('id', 1)
|
|
.execute();
|
|
```
|
|
|
|
</TabItem>
|
|
|
|
</Tabs>
|
|
|
|
## Passing parameters
|
|
|
|
Let's create a Function to insert a new planet into the `planets` table and return the new ID. Note that this time we're using the `plpgsql` language.
|
|
|
|
```sql
|
|
create or replace function add_planet(name text)
|
|
returns bigint
|
|
language plpgsql
|
|
as $$
|
|
declare
|
|
new_row bigint;
|
|
begin
|
|
insert into planets(name)
|
|
values (add_planet.name)
|
|
returning id into new_row;
|
|
|
|
return new_row;
|
|
end;
|
|
$$;
|
|
```
|
|
|
|
Once again, you can execute this function either inside your database using a `select` query, or with the client libraries:
|
|
|
|
<Tabs
|
|
defaultValue="SQL"
|
|
values={[
|
|
{label: 'SQL', value: 'SQL'},
|
|
{label: 'JS', value: 'JS'},
|
|
{label: 'Dart', value: 'Dart'},
|
|
]}>
|
|
|
|
<TabItem value="SQL">
|
|
|
|
```sql
|
|
select * from add_planet('Jakku');
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="JS">
|
|
|
|
```js
|
|
const { data, error } = await supabase.rpc('add_planet', { name: 'Jakku' })
|
|
```
|
|
|
|
</TabItem>
|
|
<TabItem value="Dart">
|
|
|
|
```dart
|
|
final res = await supabase
|
|
.rpc('add_planet', params: { 'name': 'Jakku' })
|
|
.execute();
|
|
```
|
|
|
|
</TabItem>
|
|
|
|
</Tabs>
|
|
|
|
## Suggestions
|
|
|
|
### Database Functions vs Edge Functions
|
|
|
|
For data-intensive operations, use Database Functions, which are executed within your database
|
|
and can be called remotely using the [REST and GraphQL API](../api).
|
|
|
|
For use-cases which require low-latency, use [Edge Functions](../../guides/functions), which are globally-distributed and can be written in Typescript.
|
|
|
|
### Security `definer` vs `invoker`
|
|
|
|
Postgres allows you to specify whether you want the function to be executed as the user _calling_ the function (`invoker`), or as the _creator_ of the function (`definer`). For example:
|
|
|
|
```sql
|
|
create function hello_world()
|
|
returns text
|
|
language plpgsql
|
|
security definer set search_path = public
|
|
as $$
|
|
begin
|
|
select 'hello world';
|
|
end;
|
|
$$;
|
|
```
|
|
|
|
It is best practice to use `security invoker` (which is also the default). If you ever use `security definer`, you _must_ set the `search_path`.
|
|
This limits the potential damage if you allow access to schemas which the user executing the function should not have.
|
|
|
|
### Function privileges
|
|
|
|
By default, database functions can be executed by any role. You can restrict this by altering the default privileges and then choosing which roles can execute functions.
|
|
|
|
```sql
|
|
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
|
|
|
-- Choose which roles can execute functions
|
|
GRANT EXECUTE ON FUNCTION hello_world TO authenticated;
|
|
GRANT EXECUTE ON FUNCTION hello_world TO service_role;
|
|
```
|
|
|
|
## Resources
|
|
|
|
- Official Client libraries: [JavaScript](../../reference/javascript/rpc) and [Dart](../../reference/dart/rpc)
|
|
- Community client libraries: [github.com/supabase-community](https://github.com/supabase-community)
|
|
- PostgreSQL Official Docs: [Chapter 9. Functions and Operators](https://www.postgresql.org/docs/current/functions.html)
|
|
- PostgreSQL Reference: [CREATE FUNCTION](https://www.postgresql.org/docs/9.1/sql-createfunction.html)
|
|
|
|
## Deep Dive
|
|
|
|
### Create Database Functions
|
|
|
|
<div class="video-container">
|
|
<iframe
|
|
src="https://www.youtube-nocookie.com/embed/MJZCCpCYEqk"
|
|
frameBorder="1"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
></iframe>
|
|
</div>
|
|
|
|
### Call Database Functions using JavaScript
|
|
|
|
<div class="video-container">
|
|
<iframe
|
|
src="https://www.youtube-nocookie.com/embed/I6nnp9AINJk"
|
|
frameBorder="1"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
></iframe>
|
|
</div>
|
|
|
|
### Using Database Functions to call an external API
|
|
|
|
<div class="video-container">
|
|
<iframe
|
|
src="https://www.youtube-nocookie.com/embed/rARgrELRCwY"
|
|
frameBorder="1"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
></iframe>
|
|
</div>
|