PostgreSQL:Function
NULLIF
- exp1값과 exp2값이 동일하면 NULL을 그렇지 않으면 exp1을 반환
-
CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END
현재 접속한 Role/User 확인
DO
Function 을 개발/테스트할때 Function 문법/기능을 확인하고 싶을때가 있다. Function 을 생성하고 테스트한 뒤 Function 을 삭제하는 것은 너무 비효율적이며 이런 상황에서 사용하는 것이 DO 기능/SQL명령어이다.
DO 는 Parameter가 없고 return void 방식의 임시 Function 이라고 생각하면 될 것 같다.
COALESCE
Null 체크할 때 사용한다.
대체 값은 컬럼이 null인 경우 대체 값으로 반환한다. (다른 컬럼으로 대체할 수 있다)
Example
컬럼
지정 문자열
null
json
Trigger Functions
트리거 함수 보안 정리
기본 원칙: 트리거 함수는 기본적으로 SECURITY INVOKER로 실행됩니다. 모든 트리거 함수에 SECURITY DEFINER가 필요한 것은 아닙니다.
SECURITY DEFINER가 필요한 경우
다른 테이블을 조회/수정하거나 RLS를 우회해야 하는 경우:
| 함수 | 이유 |
| | |
| | |
| | |
이 경우 반드시 SET search_path = ''을 함께 설정해야 합니다 (search_path injection 방지).
SECURITY DEFINER가 불필요한 경우
자기 자신 테이블의 NEW/OLD 레코드만 조작하는 경우:
| 함수 | 이유 |
| | NEW/OLD만 사용 (immutable 보호) |
| | NEW/OLD만 사용 (immutable 보호) |
| | NEW/OLD만 사용 (immutable 보호) |
이런 함수는 외부 테이블 접근이 없으므로 기본 SECURITY INVOKER로 충분합니다.
트리거 함수의 Supabase RPC 호출
트리거 함수는 RPC로 호출할 수 없습니다.
이유: 트리거 함수의 반환 타입이 TRIGGER이기 때문입니다.
-- 트리거 함수 시그니처
CREATE FUNCTION handle_new_org() RETURNS TRIGGER ...
-- Supabase RPC 호출 시
SELECT public.handle_new_org();
-- ERROR: trigger functions can only be called as triggers
Supabase RPC(supabase.rpc('function_name'))는 내부적으로 SELECT function_name()을 실행하는데, RETURNS TRIGGER 함수는 트리거 컨텍스트(NEW, OLD, TG_OP 등)에서만 실행 가능합니다.
RPC로 호출 가능한 함수: RETURNS void, RETURNS json, RETURNS setof record 등 일반 반환 타입만 가능합니다.
따라서 현재 프로젝트의 트리거 함수들은 RPC를 통한 직접 호출 위험이 없습니다. SECURITY DEFINER가 설정된 함수라도 트리거 이벤트를 통해서만 실행됩니다.
CREATE FUNCTION
문법만 보면 뭔가 복잡하다:
CREATE [ OR REPLACE ] FUNCTION
name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } default_expr ] [, ...] ] )
[ RETURNS rettype
| RETURNS TABLE ( column_name column_type [, ...] ) ]
{ LANGUAGE lang_name
| TRANSFORM { FOR TYPE type_name } [, ... ]
| WINDOW
| { IMMUTABLE | STABLE | VOLATILE }
| [ NOT ] LEAKPROOF
| { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT }
| { [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER }
| PARALLEL { UNSAFE | RESTRICTED | SAFE }
| COST execution_cost
| ROWS result_rows
| SUPPORT support_function
| SET configuration_parameter { TO value | = value | FROM CURRENT }
| AS 'definition'
| AS 'obj_file', 'link_symbol'
| sql_body
} ...
Increment an integer, making use of an argument name, in PL/pgSQL:
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
BEGIN
RETURN i + 1;
END;
$$ LANGUAGE plpgsql;
Return a record containing multiple output parameters:
CREATE FUNCTION dup(in int, out f1 int, out f2 text)
AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
LANGUAGE SQL;
SELECT * FROM dup(42);
security option
security 옵션은 다음과 같다.
- 함수 생성자가 아닌 호출자 권한으로 수행 - security invoker (디폴트)
- 함수 생성자 권한으로 수행 - security definer
Example:
-- 3. Create security definer function, which should be run as "postgres"
create function private.get_teams_for_authenticated_user()
returns setof bigint
language sql
security definer -- 실행 권한을 실행한 측이 아닌 함수 정의한 측(definer)로 지정.
set search_path = public -- 검색 경로 'public' schema 로 제한
stable -- 내용을 수정하지 않는다.
as $$
select team_id
from members
where user_id = auth.uid()
$$;
함수의 변동성(volatility) 분류
세 가지 분류:
- VOLATILE - 호출할 때마다 결과가 달라질 수 있음 (기본값)
- STABLE - 같은 트랜잭션 내에서 동일 인자 → 동일 결과 보장
- IMMUTABLE - 언제나 동일 인자 → 동일 결과 보장
STABLE
- 함수가 DB를 수정하지 않고, 같은 트랜잭션 안에서는 결과가 변하지 않음을 선언
- 플래너가 이를 믿고 불필요한 재호출을 생략하는 최적화를 할 수 있음
- 대표적 예:
NOW(),current_user, 테이블을 SELECT만 하는 조회 함수
CREATE FUNCTION get_org_name(org_id UUID)
RETURNS TEXT
LANGUAGE sql
STABLE -- 같은 트랜잭션 내에선 org 이름이 안 바뀜
AS $$
SELECT name FROM orgs WHERE id = org_id;
$$;
주의:
- STABLE 함수는 인덱스 표현식의 DEFAULT 값으로 사용 불가 (IMMUTABLE만 가능)
- 잘못 분류하면 플래너가 캐싱된 결과를 재사용해서 틀린 결과를 줄 수 있으므로, 실제 동작에 맞게 선언해야 합니다.