(KO) PostgreSQL 함수 매니저(fmgr) — V1 호출 규약, 조회 파이프라인, 확장 ABI
목차
- 학술적 배경
- DBMS 공통 설계 패턴
- PostgreSQL의 구현
- 소스 코드 가이드
- 소스 검증 (2026-06-05 기준)
- PostgreSQL 너머 — 비교 설계와 연구 프론티어
- 출처
학술적 배경
섹션 제목: “학술적 배경”사용자 정의 함수(UDF, user-defined function)를 지원하는 관계형 엔진은 호출 시점에 두 질문에 답해야 한다. 코드는 어디 있는가와 인수와 결과를 어떻게 주고받는가다. 이 답이 엔진의 **함수 호출 인터페이스(FCI, function-call interface)**를 형성한다. FCI는 쿼리 실행기와 내장 함수, SQL 표현식, C 확장, PL/pgSQL 프로시저 사이의 계약이다.
FCI 설계에는 두 가지 핵심 긴장이 있다.
-
조회 비용 대 호출 비용. 함수 OID를 호출 가능한 주소로 해석하려면 카탈로그 접근이 필요하다. 수백만 행에 같은 함수를 적용하는 쿼리에서 행마다 조회 비용을 지불하면 성능이 무너진다. 표준적 해법은 경로를 두 단계로 나누는 것이다. 한 번만 실행하는 조회 단계에서 캐시 가능한 디스크립터를 만들고, 호출 단계에서는 그 디스크립터만 사용해 카탈로그를 다시 건드리지 않는다. 이 분리는 쿼리 계층의 prepared statement와 plan cache가 동기를 공유한다.
-
타입 안전성 대 균일성. C 직접 호출은 인수 타입을 정밀하게 표현하지만, 호출자를 특정 시그니처 하나에 묶는다. 새 타입마다 새 진입점이 필요해진다. 엔진들은 대신 모든 인수와 반환값을 단일한 넓은 타입으로 통일한다. PostgreSQL은
Datum을 사용한다. 64비트 플랫폼에서 스칼라 값을 직접 담거나 힙 할당 값의 포인터를 담기에 충분한uintptr_t다. 타입별 변환 매크로 (PG_GETARG_INT32,PG_RETURN_FLOAT8)가 경계에서 타입 안전성을 복원한다.
프로덕션 SQL 엔진의 실질적 요구에서 세 가지 추가 기능이 나온다.
- strict-null 단락(short-circuit). strict로 표시된 함수는 인수 중 하나라도 NULL이면 호출하지 않는다. 결과는 묵시적으로 NULL이다. 이를 매니저가 강제하면 모든 함수 본체에서 방어적 코드가 사라진다.
- 집합 반환 함수(SRF, set-returning function). SQL은 함수가 행의 집합을 반환하도록 허용한다. SQL Server의 테이블값 함수, Oracle의 파이프라인 함수에 해당한다. 호출 규약은 값-per-호출(이터레이터) 방식과 일괄 구체화(materialize) 방식을 지원해야 한다.
- 확장 ABI 안정성. 한 마이너 버전에서 컴파일된 동적 모듈은 비호환 메이저 버전에서 로드를 거부해야 한다. 로더가 사용자 함수를 호출하기 전에 잘 알려진 심볼을 확인하는 magic-struct 패턴이 표준 해법이다.
이 세 가지에 대한 PostgreSQL의 답은 fmgr.h, fmgr.c, 그리고 해당
서브시스템의 1차 설계 문서인 src/backend/utils/fmgr/README에 담겨 있다.
DBMS 공통 설계 패턴
섹션 제목: “DBMS 공통 설계 패턴”아래 패턴들은 PostgreSQL, Oracle, DB2, SQL Server, MySQL 전반에서 반복된다. 다음 절의 PostgreSQL 구체적 선택들은 이 공유 공간 안에서 조정한 다이얼 값이다.
두 단계 조회 + 호출 디스크립터
섹션 제목: “두 단계 조회 + 호출 디스크립터”거의 모든 엔진은 함수 해석(코드 주소 찾기, 카탈로그 메타데이터 읽기)과
함수 호출(인수 전달, 결과 수집)을 분리한다. 해석된 메타데이터는 실행기가
쿼리(또는 플랜) 수명 동안 캐시하는 per-function 디스크립터 구조체에 저장된다.
디스크립터에는 코드 포인터, 기대 인수 수, strictness 플래그, 그리고 언어
핸들러가 파싱된 함수 상태를 캐시하는 per-call 스크래치 슬롯(fn_extra)이 담긴다.
Datum 균일 호출 규약
섹션 제목: “Datum 균일 호출 규약”타입마다 별도 C 진입점(add_int32, add_float8 …) 대신 엔진들은 임의의
값을 담기에 충분한 단일 스칼라 타입을 사용한다. Oracle은 타입 코드가 담긴
dvoid *를, SQL Server는 자체 변형 타입 구조체를, MySQL은 Item 계층을 쓴다.
PostgreSQL은 Datum을 사용한다. 모든 인수는 NullableDatum[] 배열로 도착하고,
모든 결과는 Datum + isnull 플래그로 반환된다.
언어 디스패처 간접 참조
섹션 제목: “언어 디스패처 간접 참조”함수의 call handler는 그 자체가 pg_language에 등록된 C 함수다.
매니저가 PL/pgSQL 함수를 해석할 때 PL/pgSQL 바이트코드를 직접 호출하지 않는다.
PL/pgSQL call handler를 호출하고, handler가 바이트코드를 해석한다.
handler는 매니저가 C 함수에 전달했을 동일한 FunctionCallInfo를 받는다.
따라서 매니저 관점에서 조회 이후 모든 언어는 동일하게 보인다.
세션 수준 공유 라이브러리 캐시
섹션 제목: “세션 수준 공유 라이브러리 캐시”함수 호출마다 .so/.dll을 로드하는 비용은 너무 크다. 엔진들은 파일 경로를
키로 하는 세션 수준 열린 라이브러리 핸들 캐시를 유지한다. 어떤 라이브러리의
함수를 처음 호출할 때 파일을 dlopen하고 주소를 해석한다. 이후 호출은 캐시된
핸들을 재사용한다. PostgreSQL은 두 번째 계층을 추가한다. (fn_oid, xmin, ctid)를
키로 해석된 C 함수 포인터를 저장하는 해시 테이블이다. 반복 호출에서 dlsym조차
건너뛸 수 있는 구조다.
PG_MODULE_MAGIC / ABI 가드
섹션 제목: “PG_MODULE_MAGIC / ABI 가드”동적 로드 확장을 지원하는 엔진은 다른 ABI로 컴파일된 모듈의 로드를 차단해야
한다. 로더는 사용자 함수를 호출하기 전에 로드된 라이브러리에서 잘 알려진
심볼(PostgreSQL에서는 Pg_magic_func)을 찾는다. 그 심볼은 ABI에 중요한
상수들의 구조체를 반환한다. 로더는 이를 자신의 컴파일 타임 값과 비교한다.
불일치 시 유용한 오류 메시지와 함께 로드를 중단한다.
이론 ↔ PostgreSQL 매핑
섹션 제목: “이론 ↔ PostgreSQL 매핑”| 개념 / 패턴 | PostgreSQL 이름 |
|---|---|
| 함수 조회 디스크립터 | FmgrInfo (fmgr.h:56) |
| per-call 인수/결과 컨테이너 | FunctionCallInfoBaseData / FunctionCallInfo (fmgr.h:85) |
| 균일 값 타입 | Datum (postgres.h) |
| 디스크립터 내 코드 포인터 | FmgrInfo.fn_addr — PGFunction typedef |
| 언어 디스패처(call handler) | PL 함수의 경우 fn_addr = 언어의 lanplcallfoid |
| 세션 수준 라이브러리 캐시 | fmgr.c의 CFuncHash 해시 테이블 |
| 내장 함수 테이블 | fmgr_builtins[] + fmgr_builtin_oid_index[] (fmgrtab.h) |
| ABI 가드 | Pg_magic_struct / PG_MODULE_MAGIC 매크로 (fmgr.h) |
| strict-null 단락 | FmgrInfo.fn_strict; FunctionCallInvoke 전 호출자가 검사 |
| 집합 반환 함수 지원 | fcinfo->resultinfo의 ReturnSetInfo 노드 |
| per-call 핸들러 스크래치 슬롯 | FmgrInfo.fn_extra |
| security-definer / proconfig 래퍼 | fmgr_security_definer 인터포저 함수 |
PostgreSQL의 구현
섹션 제목: “PostgreSQL의 구현”PostgreSQL의 함수 매니저, 보편적으로 fmgr로 불리는 이 컴포넌트는
실행기가 모든 SQL 호출 가능 단위를 부를 때 통과하는 단일 관문이다.
인트리 src/backend/utils/fmgr/README가 설계 권위자다. 이 절은
REL_18_STABLE 소스(커밋 273fe94)를 기준으로 그 내용을 정리한다.
구조는 세 계층으로 나뉜다.
- 디스크립터 계층 —
FmgrInfo와fmgr_info를 통한 채워 넣기. - 호출 계층 —
FunctionCallInfoBaseData,FunctionCallInvoke, 그리고DirectFunctionCall/FunctionCallNcoll/OidFunctionCallNcoll패밀리. - 확장 ABI 계층 —
PG_FUNCTION_INFO_V1,PG_MODULE_MAGIC, 그리고dfmgr.c라이브러리 로더.
계층 1 — FmgrInfo 디스크립터
섹션 제목: “계층 1 — FmgrInfo 디스크립터”FmgrInfo는 쿼리(또는 플랜)당 한 번씩 함수 OID를 해석한 결과다.
// FmgrInfo — src/include/fmgr.htypedef struct FmgrInfo{ PGFunction fn_addr; /* pointer to function or handler to be called */ Oid fn_oid; /* OID of function (NOT of handler, if any) */ short fn_nargs; /* number of input args (0..FUNC_MAX_ARGS) */ bool fn_strict; /* function is "strict" (NULL in => NULL out) */ bool fn_retset; /* function returns a set */ unsigned char fn_stats; /* collect stats if track_functions > this */ void *fn_extra; /* extra space for use by handler */ MemoryContext fn_mcxt; /* memory context to store fn_extra in */ fmNodePtr fn_expr; /* expression parse tree for call, or NULL */} FmgrInfo;fn_addr는 호출 계층이 실행 시점에 사용하는 유일한 필드다. 내장 함수라면
C 함수 주소가 직접 들어간다. C 확장이면 dlopen으로 해석한 심볼 주소다.
PL/pgSQL, PL/Python 등 절차적 언어(PL)의 경우에는 해당 언어의 call handler
주소가 들어간다. handler는 fn_oid를 사용해 실제 함수 본체를 찾는다.
fn_extra는 handler의 per-call 캐시 슬롯이다. PL/pgSQL handler는 첫 번째
호출 이후 컴파일된 함수 파스 트리를 여기에 저장해 이후 호출에서 재파싱을
건너뛴다.
fmgr_info(공개 진입점)는 fmgr_info_cxt_security에 위임한다. 실제 분기
로직은 여기에 있다.
// fmgr_info_cxt_security — src/backend/utils/fmgr/fmgr.cstatic voidfmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, bool ignore_security){ const FmgrBuiltin *fbp; /* ... */ if ((fbp = fmgr_isbuiltin(functionId)) != NULL) { /* Fast path: built-in, skip pg_proc lookup */ finfo->fn_nargs = fbp->nargs; finfo->fn_strict = fbp->strict; finfo->fn_addr = fbp->func; finfo->fn_oid = functionId; return; } /* Otherwise look up pg_proc via syscache */ procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); /* ... */ if (!ignore_security && (procedureStruct->prosecdef || ... || FmgrHookIsNeeded(functionId))) { finfo->fn_addr = fmgr_security_definer; /* wrap in security layer */ return; } switch (procedureStruct->prolang) { case INTERNALlanguageId: /* alias for a built-in */ fbp = fmgr_lookupByName(prosrc); finfo->fn_addr = fbp->func; break; case ClanguageId: fmgr_info_C_lang(functionId, finfo, procedureTuple); break; case SQLlanguageId: finfo->fn_addr = fmgr_sql; break; default: fmgr_info_other_lang(functionId, finfo, procedureTuple); break; } finfo->fn_oid = functionId; ReleaseSysCache(procedureTuple);}분기는 네 개의 리프로 끝난다. 각각 prolang 값에 대응한다.
prolang 값 | fn_addr에 설정되는 값 | 비고 |
|---|---|---|
| 내장 함수(빠른 경로) | fbp->func (syscache 불필요) | fmgr_isbuiltin은 fmgr_builtin_oid_index[]를 O(1) 배열 조회 |
INTERNALlanguageId | fmgr_lookupByName을 통한 fbp->func | 내장 함수 사용자 별칭; 느린 경로 |
ClanguageId | dlopen 해석 심볼 (CFuncHash 경유) | fmgr_info_C_lang이 dlopen + CFuncHash 처리 |
SQLlanguageId | fmgr_sql (SQL 함수 평가기) | SQL 함수 본체를 인라인 해석 |
| 그 외 | 언어의 lanplcallfoid | fmgr_info_other_lang이 pg_language 조회 |
내장 함수 빠른 경로. fmgr_isbuiltin은 syscache 조회를 완전히 피한다.
// fmgr_isbuiltin — src/backend/utils/fmgr/fmgr.cstatic const FmgrBuiltin *fmgr_isbuiltin(Oid id){ uint16 index; if (id > fmgr_last_builtin_oid) return NULL; index = fmgr_builtin_oid_index[id]; if (index == InvalidOidBuiltinMapping) return NULL; return &fmgr_builtins[index];}fmgr_builtins[]와 fmgr_builtin_oid_index[]는 빌드 시스템이 pg_proc.dat에서
fmgrtab.c로 코드 생성한 배열이다. 인덱스 배열은 OID를 슬롯으로 O(1) 단순
배열 참조로 매핑한다. 내장 함수 경로는 락 없이 몇 개의 명령어로 끝난다.
C 확장 캐시. fmgr_info_C_lang은 pg_proc에서 prosrc(심볼 이름)와
probin(라이브러리 파일 경로)을 읽고, load_external_function을 호출해 .so를
열며(dfmgr.c가 dlopen 핸들을 캐시), fetch_finfo_record로 PG_FUNCTION_INFO_V1
디스크립터를 검증한 뒤 해석된 주소를 (fn_oid, xmin, ctid) 키로 CFuncHash에
저장한다. 캐시된 xmin/ctid를 현재 pg_proc 튜플과 비교해 오래된 항목을
교체하는 구조다.
security-definer 인터포저. prosecdef가 true이거나, proconfig가 설정되어
있거나, 플러그인 훅(fmgr_hook)이 활성화된 경우 fn_addr는 실제 함수 대신
fmgr_security_definer로 설정된다. 호출 시점에 fmgr_security_definer는
사용자 ID를 전환하고 GUC 오버라이드를 적용한 뒤, fn_extra에 캐시된 내부
FmgrInfo로 실제 함수를 호출한다. 이 구조 덕분에 호출자는 security-definer
여부를 인식하지 않아도 된다.
계층 2 — FunctionCallInfo 호출 계층
섹션 제목: “계층 2 — FunctionCallInfo 호출 계층”FunctionCallInfoBaseData(FunctionCallInfo로 typedef)는 모든 함수에
전달되는 per-call 컨테이너다.
// FunctionCallInfoBaseData — src/include/fmgr.htypedef struct FunctionCallInfoBaseData{ FmgrInfo *flinfo; /* ptr to lookup info used for this call */ fmNodePtr context; /* pass info about context of call */ fmNodePtr resultinfo; /* pass or return extra info about result */ Oid fncollation; /* collation for function to use */ bool isnull; /* function must set true if result is NULL */ short nargs; /* # arguments actually passed */ NullableDatum args[]; /* flexible array of (Datum value, bool isnull) */} FunctionCallInfoBaseData;context는 호출 컨텍스트 정보를 담는 Node *다. 트리거 함수에는
TriggerData, 집계/윈도우 함수에는 AggState / WindowAggState, 저장
프로시저에는 CallContext, 소프트 오류 호출자에는 ErrorSaveContext가 온다.
IsA(context, X) 패턴으로 함수가 자신의 호출 컨텍스트를 감지한다.
resultinfo는 집합 반환 함수에 ReturnSetInfo를 전달한다.
LOCAL_FCINFO로 스택 할당. FunctionCallInfoBaseData는 인수를 위한 가변
길이 배열 멤버를 가지므로, 호출자는 LOCAL_FCINFO 매크로로 적절한 크기의
구조체를 스택에 할당한다.
// LOCAL_FCINFO — src/include/fmgr.h#define LOCAL_FCINFO(name, nargs) \ union { \ FunctionCallInfoBaseData fcinfo; \ char fcinfo_data[SizeForFunctionCallInfo(nargs)]; \ } name##data; \ FunctionCallInfo name = &name##data.fcinfounion이 정렬을 보장한다. SizeForFunctionCallInfo(nargs)는
offsetof(args) + sizeof(NullableDatum) * nargs를 계산한다.
힙 할당에는 palloc(SizeForFunctionCallInfo(nargs))를 사용한다.
호출 매크로. 실제 호출은 단일 간접 함수 호출 하나다.
// FunctionCallInvoke — src/include/fmgr.h#define FunctionCallInvoke(fcinfo) ((* (fcinfo)->flinfo->fn_addr) (fcinfo))세 가지 호출 지점 패턴. 호출자는 가용한 컨텍스트에 따라 세 패밀리 중 하나를 선택한다.
// DirectFunctionCall1Coll — src/backend/utils/fmgr/fmgr.c// For calling a known PGFunction pointer directly; no FmgrInfo needed.DatumDirectFunctionCall1Coll(PGFunction func, Oid collation, Datum arg1){ LOCAL_FCINFO(fcinfo, 1); InitFunctionCallInfoData(*fcinfo, NULL, 1, collation, NULL, NULL); fcinfo->args[0].value = arg1; fcinfo->args[0].isnull = false; result = (*func) (fcinfo); if (fcinfo->isnull) elog(ERROR, "function %p returned NULL", (void *) func); return result;}세 패밀리와 그 용도는 다음과 같다.
| 패밀리 | 사용 시점 | 비고 |
|---|---|---|
DirectFunctionCallNcoll | PGFunction*를 직접 보유; NULL 인수/결과 불허 | FmgrInfo 없음; non-NULL 결과를 단언 |
FunctionCallNcoll | FmgrInfo * 보유 (이전 fmgr_info에서) | pgstat_init_function_usage 추적; NULL 허용 |
OidFunctionCallNcoll | OID만 있을 때 | fmgr_info + FunctionCallNcoll 인라인 호출 |
C 함수 작성 규약. 모든 fmgr 호출 가능 C 함수는
Datum func(PG_FUNCTION_ARGS) 시그니처를 가진다. PG_FUNCTION_ARGS는
FunctionCallInfo fcinfo로 확장된다. 인수는 타입별 매크로로 꺼내고
결과도 같은 방식으로 반환한다.
// Example fmgr-callable function using V1 conventionDatumint4add(PG_FUNCTION_ARGS){ int32 arg1 = PG_GETARG_INT32(0); int32 arg2 = PG_GETARG_INT32(1); PG_RETURN_INT32(arg1 + arg2);}PG_GETARG_INT32(n)은 DatumGetInt32(fcinfo->args[n].value)로 확장된다.
PG_RETURN_INT32(x)는 return Int32GetDatum(x)로 확장된다.
text 같은 varlena 타입에서는 PG_GETARG_TEXT_PP(n)이 TOAST 해제 후 값을
반환하고, PG_RETURN_TEXT_P(x)가 포인터를 Datum으로 반환한다.
non-strict 함수는 PG_ARGISNULL(n)으로 NULL 여부를 먼저 확인한다.
strict 함수는 NULL 인수를 받지 않는다. 호출자의 null 검사가 먼저 발동하기
때문이다.
계층 3 — 확장 ABI
섹션 제목: “계층 3 — 확장 ABI”PG_FUNCTION_INFO_V1. 모든 C 확장 함수는 PG_FUNCTION_INFO_V1 매크로로
호출 규약을 선언해야 한다.
// PG_FUNCTION_INFO_V1 — src/include/fmgr.h#define PG_FUNCTION_INFO_V1(funcname) \extern PGDLLEXPORT Datum funcname(PG_FUNCTION_ARGS); \extern PGDLLEXPORT const Pg_finfo_record * CppConcat(pg_finfo_,funcname)(void); \const Pg_finfo_record * \CppConcat(pg_finfo_,funcname) (void) \{ \ static const Pg_finfo_record my_finfo = { 1 }; \ return &my_finfo; \} \extern int no_such_variable이 매크로는 api_version = 1을 반환하는 동반 pg_finfo_<funcname> 함수를
생성한다. fetch_finfo_record는 이름으로(psprintf("pg_finfo_%s", funcname))
이 동반 심볼을 찾아 버전을 검증한다. 심볼을 찾지 못하면 “SQL-callable functions
need an accompanying PG_FUNCTION_INFO_V1(funcname).” 힌트와 함께 오류를 발생시킨다.
버전-0(“구식”) 규약은 제거되었고, V1이 유일하게 지원되는 규약이다.
PG_MODULE_MAGIC. per-function 버전 검사 외에, 공유 라이브러리 전체의
ABI 검사도 통과해야 한다. PG_MODULE_MAGIC 매크로(또는 신형
PG_MODULE_MAGIC_EXT(...))는 잘 알려진 심볼 Pg_magic_func 아래에
Pg_magic_struct를 방출한다.
// Pg_magic_struct and Pg_abi_values — src/include/fmgr.htypedef struct{ int version; /* PostgreSQL major version */ int funcmaxargs; /* FUNC_MAX_ARGS */ int indexmaxkeys; /* INDEX_MAX_KEYS */ int namedatalen; /* NAMEDATALEN */ int float8byval; /* FLOAT8PASSBYVAL */ char abi_extra[32]; /* see pg_config_manual.h */} Pg_abi_values;dfmgr.c의 load_external_function은 새로 로드된 라이브러리에서
Pg_magic_func()를 호출하고 반환된 구조체를 서버 자체 값과 필드별로 비교한다.
불일치 시 incompatible_module_error()로 불일치 필드를 명시한 오류를 발생시킨다.
다른 NAMEDATALEN로 컴파일된 라이브러리가 구조체 레이아웃 불일치로 크래시를
일으키는 것을 막는 장치다.
소프트 오류(ErrorSaveContext)
섹션 제목: “소프트 오류(ErrorSaveContext)”PostgreSQL의 일반 오류 경로(ereport(ERROR, ...))는 longjmp로 언와인드하며
전체 서브트랜잭션 정리를 요구한다. 잘못된 형식의 입력을 거부하기만 하면 되는
데이터타입 입력 함수에는 비용이 너무 크다. PG14부터 함수는 소프트 오류로
보고할 수 있다.
// errsave / ereturn pattern — src/backend/utils/fmgr/README// Instead of: ereport(ERROR, (errcode(...), errmsg(...)));// Write: errsave(fcinfo->context, (errcode(...), errmsg(...)));// Or combine: ereturn(fcinfo->context, (Datum) 0, (errcode(...), errmsg(...)));fcinfo->context가 ErrorSaveContext 노드이면 errsave는 오류 정보를
해당 노드에 저장하고 정상 반환한다. 함수는 더미 Datum을 반환한다.
context가 NULL이거나 다른 노드 타입이면 errsave는 ereport(ERROR)와
동일하게 동작한다. 소프트 오류를 받으려는 호출자는 ErrorSaveContext를
할당해 fcinfo->context로 전달하고, 호출 후 escontext.error_occurred를
확인한다. README는 소프트 오류를 복구 가능한 상황(잘못된 입력 구문,
범위 초과 값)에만 제한한다. 내부 오류와 OOM은 여전히 하드 경로를 써야 한다.
집합 반환 함수(SRF)
섹션 제목: “집합 반환 함수(SRF)”pg_proc에서 proretset = true로 표시된 함수는 fcinfo->resultinfo에
ReturnSetInfo 노드를 받는다. 두 가지 반환 모드가 지원된다.
값-per-호출 모드(이터레이터): 함수가 반복 호출된다. 각 행에서
ReturnSetInfo.isDone을 ExprMultipleResult로 설정하고, 완료 시
ExprEndResult로 설정한다. 실행기가 루프로 호출한다. 이 모드의 함수는
마지막 호출에서 리소스를 정리하면 안 된다. 실행기가 LIMIT 등으로 조기 종료할
수 있기 때문이다.
funcapi.h 매크로가 프로토콜을 캡슐화한다. 첫 번째 호출 감지는
“fn_extra가 아직 NULL인가?”로 단순화되고, 호출 간 상태는 fn_extra에 매달린
FuncCallContext에 저장된다.
// SRF value-per-call macros — src/include/funcapi.h#define SRF_IS_FIRSTCALL() (fcinfo->flinfo->fn_extra == NULL)#define SRF_FIRSTCALL_INIT() init_MultiFuncCall(fcinfo)#define SRF_PERCALL_SETUP() per_MultiFuncCall(fcinfo)
#define SRF_RETURN_NEXT(_funcctx, _result) \ do { \ ReturnSetInfo *rsi; \ (_funcctx)->call_cntr++; \ rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ rsi->isDone = ExprMultipleResult; \ PG_RETURN_DATUM(_result); \ } while (0)
#define SRF_RETURN_DONE(_funcctx) \ do { \ ReturnSetInfo *rsi; \ end_MultiFuncCall(fcinfo, _funcctx); \ rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ rsi->isDone = ExprEndResult; \ PG_RETURN_NULL(); \ } while (0)init_MultiFuncCall은 정확히 한 번 실행된다. 함수가 집합을 받아들이는 컨텍스트에서
호출됐는지 단언(IsA(fcinfo->resultinfo, ReturnSetInfo))하고, fn_mcxt의
자식으로 “SRF multi-call context”라는 전용 AllocSetContextCreate 컨텍스트를
생성하며, FuncCallContext를 0으로 초기화해 fn_extra에 저장한다. 또한
RegisterExprContextCallback으로 shutdown_MultiFuncCall 콜백을 등록해
실행기가 스캔을 조기 종료(LIMIT)하더라도 호출 간 컨텍스트가 해제되도록 한다.
// init_MultiFuncCall — src/backend/utils/fmgr/funcapi.cFuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS){ FuncCallContext *retval; if (fcinfo->resultinfo == NULL || !IsA(fcinfo->resultinfo, ReturnSetInfo)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); if (fcinfo->flinfo->fn_extra == NULL) { ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext multi_call_ctx; multi_call_ctx = AllocSetContextCreate(fcinfo->flinfo->fn_mcxt, "SRF multi-call context", ALLOCSET_SMALL_SIZES); retval = (FuncCallContext *) MemoryContextAllocZero(multi_call_ctx, sizeof(FuncCallContext)); retval->multi_call_memory_ctx = multi_call_ctx; fcinfo->flinfo->fn_extra = retval; RegisterExprContextCallback(rsi->econtext, shutdown_MultiFuncCall, PointerGetDatum(fcinfo->flinfo)); } else elog(ERROR, "init_MultiFuncCall cannot be called more than once"); return retval;}per_MultiFuncCall은 fn_extra를 FuncCallContext *로 캐스팅하는 한 줄짜리다.
call_cntr(SRF_RETURN_NEXT가 증가)과 선택적 max_calls가 루프 카운터 역할을
하고, user_fctx는 SRF가 필요한 커서 상태를 저장하는 per-SRF 스크래치 포인터다.
shutdown 콜백이 해제를 책임지므로, README의 경고가 성립한다. 값-per-호출 SRF는
SRF_RETURN_DONE이 실행된다고 가정하면 안 된다. 파일 디스크립터나 열린 커서 같은
비메모리 리소스는 이 모드에서 안전하지 않으며, 그런 경우에는 구체화 모드를 사용해야 한다.
구체화 모드(materialize mode): 함수가 econtext->ecxt_per_query_memory에
Tuplestore를 생성하고 한 번의 호출에서 모두 채운 뒤, 포인터와 TupleDesc를
ReturnSetInfo에 저장하고 returnMode = SFRM_Materialize로 설정한다.
funcapi.c의 InitMaterializedSRF 헬퍼가 보일러플레이트를 캡슐화한다.
// InitMaterializedSRF — src/backend/utils/fmgr/funcapi.cvoidInitMaterializedSRF(FunctionCallInfo fcinfo, bits32 flags){ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; /* sanity checks on rsinfo ... */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; old_context = MemoryContextSwitchTo(per_query_ctx); tupstore = tuplestore_begin_heap(random_access, false, work_mem); MemoryContextSwitchTo(old_context); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = stored_tupdesc;}funcapi.c는 per-call 상태가 필요한 값-per-호출 SRF를 위해
init_MultiFuncCall / per_MultiFuncCall / end_MultiFuncCall도 제공한다.
상태는 fn_mcxt에 할당된 FuncCallContext에 저장된다.
호출 경로 + SRF 값-per-호출 프로토콜
섹션 제목: “호출 경로 + SRF 값-per-호출 프로토콜”아래 다이어그램은 pg_proc OID에서 시작해 두 단계 조회/호출 분리를 거쳐
반환된 Datum에 이르는 단일 값의 전체 흐름을 추적한다. 이어서 집합 반환
함수가 fn_extra 첫 번째 호출 래치(latch)로 행마다 같은 FunctionCallInvoke
엣지를 재사용하는 방식을 보여준다.
flowchart TD
OID["pg_proc OID<br/>(functionId)"]
FI["fmgr_info / fmgr_info_cxt_security<br/>(lookup phase, once per query)"]
FBP["fmgr_isbuiltin<br/>builtin? fn_addr = fbp->func"]
CL["fmgr_info_C_lang<br/>CFuncHash / fetch_finfo_record"]
OL["fmgr_info_other_lang / fmgr_sql<br/>fn_addr = call handler"]
FINFO["FmgrInfo populated<br/>fn_addr, fn_nargs, fn_strict, fn_extra"]
LF["LOCAL_FCINFO(fcinfo, nargs)<br/>+ InitFunctionCallInfoData<br/>+ fill args[].value / .isnull"]
STRICT{"fn_strict &&<br/>any arg isnull?"}
NULLOUT["result = NULL<br/>(skip the call)"]
INV["FunctionCallInvoke(fcinfo)<br/>(* fn_addr)(fcinfo)"]
BODY["function body<br/>Datum f(PG_FUNCTION_ARGS)<br/>PG_GETARG_* / PG_RETURN_*"]
RET["result Datum + fcinfo->isnull"]
OID --> FI
FI -->|"builtin fast path"| FBP
FI -->|"C language"| CL
FI -->|"SQL / PL"| OL
FBP --> FINFO
CL --> FINFO
OL --> FINFO
FINFO --> LF
LF --> STRICT
STRICT -->|"yes"| NULLOUT
STRICT -->|"no"| INV
INV --> BODY
BODY --> RET
RET -->|"fn_retset SRF only"| FIRST{"SRF_IS_FIRSTCALL?<br/>fn_extra == NULL"}
FIRST -->|"yes"| INIT["SRF_FIRSTCALL_INIT<br/>init_MultiFuncCall<br/>alloc FuncCallContext in fn_extra"]
FIRST -->|"no"| PER["SRF_PERCALL_SETUP<br/>per_MultiFuncCall"]
INIT --> PER
PER --> MORE{"call_cntr <<br/>max_calls?"}
MORE -->|"yes"| NEXT["SRF_RETURN_NEXT<br/>isDone = ExprMultipleResult<br/>executor re-invokes via fn_addr"]
MORE -->|"no"| DONE["SRF_RETURN_DONE<br/>end_MultiFuncCall<br/>isDone = ExprEndResult"]
NEXT -->|"loop"| INV
그림 2 — fmgr 호출 경로와 SRF 값-per-호출 프로토콜. 위쪽 절반은 per-value
경로다. fmgr_info가 FmgrInfo를 한 번 채우고, 이후 각 호출 스택은
LOCAL_FCINFO로 fcinfo를 할당하고, strict-null 단락을 적용하며,
FunctionCallInvoke로 디스패치한다. 아래쪽 절반(fn_retset 분기)은
값-per-호출 루프를 보여준다. fn_extra == NULL 래치가 첫 번째 호출(
FuncCallContext 할당)과 이후 호출을 구분하며, SRF_RETURN_NEXT는
SRF_RETURN_DONE이 ExprEndResult를 설정할 때까지 같은 FunctionCallInvoke
엣지를 재진입한다.
컴포넌트 다이어그램
섹션 제목: “컴포넌트 다이어그램”flowchart TD EX["실행기<br/>(ExecMakeTableFunctionResult,<br/>ExprEvalStep 등)"] FI["fmgr_info<br/>(조회 단계)"] FCI["FunctionCallInvoke<br/>(호출 단계)"] BI["fmgr_isbuiltin<br/>O(1) OID 인덱스"] CH["CFuncHash<br/>(C 확장 캐시)"] DL["dfmgr.c<br/>load_external_function<br/>+ fetch_finfo_record"] SD["fmgr_security_definer<br/>(인터포저)"] PL["fmgr_info_other_lang<br/>→ lanplcallfoid"] SQL["fmgr_sql<br/>(SQL 함수 평가기)"] FN["fn_addr<br/>(PGFunction*)"] EX -->|"fmgr_info(oid, &flinfo)"| FI FI -->|"내장 함수 OID"| BI FI -->|"C 언어"| CH CH -->|"캐시 미스"| DL FI -->|"보안/훅"| SD FI -->|"SQL 언어"| SQL FI -->|"PL 언어"| PL BI --> FN CH --> FN SD --> FN SQL --> FN PL --> FN FN -->|"FunctionCallInvoke(fcinfo)"| FCI EX -->|"LOCAL_FCINFO + 인수"| FCI
그림 1 — fmgr 컴포넌트 흐름. 실행기는 두 단계를 별도로 구동한다. 조회 단계(왼쪽)는
fn_addr가 설정된 FmgrInfo를 생성하고, 호출 단계(오른쪽)는 fn_addr로
FunctionCallInfo를 전달한다. CFuncHash는 반복 호출 시 dfmgr.c 라이브러리
로드를 건너뛴다. fmgr_security_definer는 prosecdef, proconfig, 또는 플러그인
훅이 활성화된 경우 fn_addr로 삽입된다.
소스 코드 가이드
섹션 제목: “소스 코드 가이드”조회 파이프라인
섹션 제목: “조회 파이프라인”fmgr_info— 공개 진입점.CurrentMemoryContext와ignore_security = false로fmgr_info_cxt_security에 위임한다.fmgr_info_cxt— 동일하지만 부가 데이터를 위한 명시적MemoryContext를 받는다.fmgr_info_cxt_security— 분기 핵심. 내장 함수 빠른 경로 검사, syscache 조회,prolang스위치.fmgr_isbuiltin—fmgr_builtin_oid_index에서 O(1) 배열 조회.FmgrBuiltin *또는 NULL을 반환한다.fmgr_lookupByName—fmgr_builtins[]를 이름으로 선형 탐색.INTERNALlanguageId별칭에만 사용된다.fmgr_info_C_lang— C 확장 경로.CFuncHash에서lookup_C_func, 그 다음load_external_function+fetch_finfo_record, 그 다음record_C_func.fetch_finfo_record—pg_finfo_<name>심볼 찾기, 호출,api_version == 1검증.lookup_C_func/record_C_func—CFuncHashget/set.xmin+ctid로 오래된 항목 감지.fmgr_info_other_lang— PL handler 경로.ignore_security = true로fmgr_info_cxt_security를 재귀 호출해lanplcallfoid를 해석한다.fmgr_security_definer— 인터포저. 실제FmgrInfo+userid+ GUC 목록을fn_extra에 캐시하고 호출 후 복원한다.fmgr_info_copy—FmgrInfo얕은 복사.fn_extra를 0으로 만들어 handler 상태가 별칭되지 않도록 한다.fmgr_symbol— 유틸리티. OID로 C 심볼을 식별하는(mod, fn)문자열을 반환.pg_get_function_sqlbody와 JIT에서 사용된다.fmgr_internal_function— 역방향 조회.fmgr_builtins[]에서 이름으로 OID를 찾는다.
호출 계층
섹션 제목: “호출 계층”FunctionCallInvoke— 매크로.fn_addr를 통한 단일 간접 호출.InitFunctionCallInfoData— 매크로.args[]제외,FunctionCallInfoBaseData의 모든 스칼라 필드를 채운다.LOCAL_FCINFO— 매크로. 올바른 크기의FunctionCallInfoBaseData를 스택에 할당.DirectFunctionCallNColl(1–9) — 알려진PGFunction포인터 직접 호출.FmgrInfo없음. NULL 인수/결과 불허.CallerFInfoFunctionCall1/2—DirectFunctionCall과 유사하지만FmgrInfo *를 받는다. 호출자가 보유한fn_extra를 지속시키고 싶을 때 유용하다.FunctionCallNColl(0–9) —FmgrInfo *를 통한 호출.pgstat_init_function_usage추적.OidFunctionCallNColl(0–9) — OID로 호출. 인라인으로fmgr_info+FunctionCallNcoll수행.
확장 ABI
섹션 제목: “확장 ABI”PG_FUNCTION_INFO_V1— 매크로.pg_finfo_<name>심볼 생성.PG_MODULE_MAGIC/PG_MODULE_MAGIC_EXT— 매크로.Pg_magic_struct가 담긴Pg_magic_func심볼 생성.load_external_function(dfmgr.c) —dlopen+dlsym과 라이브러리 캐시. ABI 검사를 위해Pg_magic_func호출.fetch_finfo_record—pg_finfo_<name>찾기 + 검증.
집합 반환 함수
섹션 제목: “집합 반환 함수”InitMaterializedSRF— 구체화 모드 SRF를 위해ReturnSetInfo에Tuplestore+TupleDesc를 설정한다.init_MultiFuncCall/per_MultiFuncCall/end_MultiFuncCall—FuncCallContext를 통한 값-per-호출 SRF 상태 관리.get_call_result_type— 호출 표현식에서 다형 함수의 실제 반환 타입을 해석한다. SRF가TupleDesc를 구성할 때 사용한다.
위치 힌트 (2026-06-05 기준, 커밋 273fe94)
섹션 제목: “위치 힌트 (2026-06-05 기준, 커밋 273fe94)”| 심볼 | 파일 | 라인 |
|---|---|---|
FmgrInfo | src/include/fmgr.h | 56 |
FunctionCallInfoBaseData | src/include/fmgr.h | 85 |
PGFunction typedef | src/include/fmgr.h | 40 |
LOCAL_FCINFO | src/include/fmgr.h | 110 |
InitFunctionCallInfoData | src/include/fmgr.h | 150 |
FunctionCallInvoke | src/include/fmgr.h | 172 |
PG_FUNCTION_ARGS | src/include/fmgr.h | 193 |
PG_ARGISNULL | src/include/fmgr.h | 209 |
PG_RETURN_NULL | src/include/fmgr.h | 345 |
PG_FUNCTION_INFO_V1 | src/include/fmgr.h | 415 |
Pg_finfo_record | src/include/fmgr.h | 394 |
Pg_abi_values | src/include/fmgr.h | 466 |
Pg_magic_struct | src/include/fmgr.h | 478 |
FmgrBuiltin | src/include/utils/fmgrtab.h | 25 |
fmgr_isbuiltin | src/backend/utils/fmgr/fmgr.c | 76 |
fmgr_lookupByName | src/backend/utils/fmgr/fmgr.c | 101 |
fmgr_info | src/backend/utils/fmgr/fmgr.c | 127 |
fmgr_info_cxt | src/backend/utils/fmgr/fmgr.c | 137 |
fmgr_info_cxt_security | src/backend/utils/fmgr/fmgr.c | 147 |
fmgr_symbol | src/backend/utils/fmgr/fmgr.c | 281 |
fmgr_info_C_lang | src/backend/utils/fmgr/fmgr.c | 349 |
fmgr_info_other_lang | src/backend/utils/fmgr/fmgr.c | 418 |
fetch_finfo_record | src/backend/utils/fmgr/fmgr.c | 455 |
lookup_C_func | src/backend/utils/fmgr/fmgr.c | 515 |
record_C_func | src/backend/utils/fmgr/fmgr.c | 539 |
fmgr_info_copy | src/backend/utils/fmgr/fmgr.c | 580 |
fmgr_internal_function | src/backend/utils/fmgr/fmgr.c | 595 |
fmgr_security_definer | src/backend/utils/fmgr/fmgr.c | 632 |
DirectFunctionCall1Coll | src/backend/utils/fmgr/fmgr.c | 792 |
FunctionCall1Coll | src/backend/utils/fmgr/fmgr.c | 1129 |
OidFunctionCall1Coll | src/backend/utils/fmgr/fmgr.c | 1411 |
InitMaterializedSRF | src/backend/utils/fmgr/funcapi.c | 76 |
init_MultiFuncCall | src/backend/utils/fmgr/funcapi.c | 133 |
per_MultiFuncCall | src/backend/utils/fmgr/funcapi.c | 208 |
end_MultiFuncCall | src/backend/utils/fmgr/funcapi.c | 220 |
get_call_result_type | src/backend/utils/fmgr/funcapi.c | 276 |
FuncCallContext | src/include/funcapi.h | 57 |
SRF_IS_FIRSTCALL | src/include/funcapi.h | 305 |
SRF_FIRSTCALL_INIT | src/include/funcapi.h | 307 |
SRF_PERCALL_SETUP | src/include/funcapi.h | 309 |
SRF_RETURN_NEXT | src/include/funcapi.h | 311 |
SRF_RETURN_DONE | src/include/funcapi.h | 329 |
소스 검증 (2026-06-05 기준)
섹션 제목: “소스 검증 (2026-06-05 기준)”검증된 사실
섹션 제목: “검증된 사실”-
fmgr_isbuiltin은 이진 탐색이 아닌 O(1) 배열 조회다.fmgr.c:76–93에서 확인. 코드 생성된uint16배열fmgr_builtin_oid_index[id]를 읽어&fmgr_builtins[index]를 반환한다. 반복이 없다. “fast lookup only possible if original oid still assigned” 주석이 재할당된 OID(사용자CREATE FUNCTION ... INTERNAL)는fmgr_lookupByName선형 탐색으로 내려감을 명시한다. -
CFuncHash는 OID만이 아닌xmin+ctid로 오래된 항목을 감지한다.lookup_C_func(fmgr.c:515–533)에서 확인.fn_xmin == HeapTupleHeaderGetRawXmin(t_data)와ItemPointerEquals(&fn_tid, &t_self)두 조건이 모두 참일 때만 캐시 항목을 수락한다. 세션 재시작 없이pg_proc튜플 업데이트(ALTER FUNCTION)를 감지하는 구조다. -
V0 호출 규약은 완전히 제거되었다.
README가 “the V0 interface has been removed”라고 명시한다. 확인:fmgr_info_C_lang의api_version스위치 (fmgr.c:399–410)에case 1만 있다. 그 외 값은 모두elog(ERROR, "unrecognized function API version: %d")로 떨어진다.case 0분기가 없다. -
fmgr_security_definer는 외부fcinfo에 투명하다.fmgr.c:738–777에서 확인.fcinfo->flinfo를 저장하고, 캐시된 내부FmgrInfo로 교체한 뒤,FunctionCallInvoke로 호출하고, 성공 경로와PG_CATCH경로 모두에서fcinfo->flinfo를 복원한다. 호출자의 인수가 담긴 외부fcinfo는 변경 없이 전달되며,flinfo만 실행 시간 동안 교체된다. -
PG_MODULE_MAGIC_EXT는 PG18에서 추가된 것이다. 인수 없이 쓰는PG_MODULE_MAGIC_EXT()는 기존PG_MODULE_MAGIC과 동일하다.fmgr.h:478–495에서 확인.Pg_magic_struct에name과version필드가 있으나 기존 매크로를 쓰면 NULL로 유지된다. 이 필드들은 ABI 검사에서 비교되지 않는다. 정보 제공용이다. -
errsave/ereturn을 통한 소프트 오류는 호출자가fcinfo->context에ErrorSaveContext *를 전달해야만 활성화된다.README§“Handling Soft Errors”에서 확인.context가 NULL이거나ErrorSaveContext가 아니면errsave는ereport(ERROR)를 호출한다. 옵트인하지 않는 호출자에게는 동작 변화가 없다.
미해결 질문
섹션 제목: “미해결 질문”-
fmgr_hook/needs_fmgr_hook플러그인 API.fmgr.c:39–40에 두 전역 함수 포인터(fmgr_hook,needs_fmgr_hook)가 선언되어 있지만 인트리 호출자가 없다. 확장 전용 포인트다. 이 훅을 설정하는 플러그인이 해야 할 일의 문서는fmgr.c의 헤더 주석에만 있다. 조사 경로:src/include/fmgr.h에서fmgr_hook_type을 검색하고 호출 지점을 추적. 이 훅을 사용하는 서드파티 확장(예:pg_hint_plan) 사례를 참고. -
CallerFInfoFunctionCall1/2사용 사례. 호출자가 제공한FmgrInfo *를 알려진PGFunction에 전달하는 이 함수들의 주석에는 “DirectFunctionCall 함수와 유사하지만 FmgrInfo가 제공된다”고만 나온다. 인트리 사용자는array_map등utils/adt/몇 곳뿐이다. 일반적인 호출 지점에서DirectFunctionCall대비 성능 이점이 있는지는 문서화되어 있지 않으며, README도 다루지 않는다. -
fn_expr설정의 일관성.FmgrInfo.fn_expr은 “호출에 대한 표현식 파스 트리, 또는 NULL”로 설명되며 “함수가 아닌 인수에 관한 정보”라고 명시된다. 실행기의 다양한 플랜 노드 타입(스캔 노드 대 프로젝션 대 집계 전이)에 걸쳐 이 필드가 얼마나 일관되게 설정되는지는 여기서 검증되지 않았다. 조사 경로:executor/에서fmgr_info_set_expr탐색.
PostgreSQL 너머 — 비교 설계와 연구 프론티어
섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 프론티어”-
Oracle의 call-spec과 PL/SQL 함수 매니저. Oracle의
CALL_SPEC메커니즘과PL/SQL 엔진 → 네이티브 컴파일파이프라인은 동일한 조회 비용/타입 안전성 긴장을 다룬다. Oracle의DETERMINISTIC과RESULT_CACHE함수 속성은 PostgreSQL의 strict +provolatile플래그에 대응한다. 나란히 비교하면 PostgreSQL 플래너가 캐시할 수 있는 것과 없는 것이 명확해진다. -
SQL Server의 CLR 통합과 함수 ABI. SQL Server의 CLR 통합은 ABI 검증에 .NET 메타데이터를 사용한다. PostgreSQL의
PG_MODULE_MAGIC보다 풍부한 버전이다. 관리/비관리 경계는 PostgreSQL의fmgr_security_definer패턴과 흥미로운 비교점이 된다. -
MySQL의 UDF ABI (xxx_init / xxx / xxx_deinit). MySQL의 세 함수 UDF API (init, main, deinit)는 PostgreSQL의
fn_extraper-call 캐시 슬롯에 직접 대응한다. 둘 다 “한 번 파싱, 여러 번 실행” 문제를 해결한다. MySQL의 ABI에는 module-magic 유사물이 없어 버전 불일치 시 오류 대신 크래시가 발생한다. -
DuckDB의 함수 등록 API. DuckDB의
ScalarFunction/TableFunctionC++ API는 동일한 두 단계 패턴(메타데이터와 함께 등록 →DataChunk로 호출)을 더 높은 추상화 수준으로 노출한다. 스칼라Datum대신 벡터화된 청크를 사용한다는 점이 차이다. PostgreSQL의 SRF 구체화 모드와 비교하면 per-tuple 대 배치 벡터화 트레이드오프를 드러낼 수 있다. PostgreSQL 자체 벡터화 연구 방향과도 연관된다. -
확장 ABI 설계의 “INIT 함수” 접근법. PostgreSQL의
_PG_init훅 (fmgr.h:434에PGDLLEXPORT선언)은 라이브러리 로드당 한 번 호출되며, background worker, 훅, 커스텀 락 매니저를 등록하는 표준 위치다. Linux의module_init/module_exit과 유사한 패턴이다. DuckDB의LoadInternal이나 SQLite의sqlite3_auto_extension과 비교하면postgres-extensions.md문서에 유용한 내용이 된다.
인트리 설계 문서
섹션 제목: “인트리 설계 문서”src/backend/utils/fmgr/README— V1 FCI의 1차 설계 문서.FmgrInfo,FunctionCallInfo, 호출 컨텍스트, TOAST 처리, SRF 모드, 소프트 오류, 함수 핸들러 노트에 대한 권위 있는 설명.
교과서 참고
섹션 제목: “교과서 참고”- Database System Concepts (Silberschatz, Korth & Sudarshan, 7판) — §9.5 “Accessing SQL from a Programming Language”와 §27 “PostgreSQL”.
소스 파일
섹션 제목: “소스 파일”src/backend/utils/fmgr/fmgr.c— 조회 파이프라인, 호출 헬퍼,CFuncHash,fmgr_security_definer.src/backend/utils/fmgr/funcapi.c— SRF 지원 (InitMaterializedSRF,MultiFuncCall,get_call_result_type).src/backend/utils/fmgr/dfmgr.c— 동적 라이브러리 로더 (dlopen캐시,load_external_function,Pg_magic_funcABI 검사).src/include/fmgr.h— fmgr 사용자를 위한 모든 공개 타입, 매크로, 함수 선언.src/include/utils/fmgrtab.h—FmgrBuiltin,fmgr_builtins[],fmgr_builtin_oid_index[].src/include/funcapi.h—ReturnSetInfo,FuncCallContext,TypeFuncClass, SRF 헬퍼 선언.