오라클에 이어 이번엔 PostgreSQL에서 C소스로 함수 생성 해 보았다.
오라클과 비교해 보자면 오라클은 리스너 설정하는 부분이 제일 까다로웠으며, C소스코드 내부에서는 평소 작성하던
C문법 그대로 사용하면 되어 DB로 리턴하는 법을 알아내는 것에 시간을 많이 소요했었지만,
PostgreSQL 같은 경우에는 PG에서 제공하는 API를 사용하여 PostgreSQL이 제공하는 데이터 타입을 찾아 소스코드를 짜야 하는 점이 어려웠던 점이었다.
이전 포스팅에서는 오라클의 경우, C내부 함수의 작성이나 데이터 타입 등 평소에 쓰던 문법과 다르지 않아 크게 C소스코드 작성법에 대해 설명하지 않았는데 PostgreSQL의 경우 C소스코드를 잘 작성해야 함수 생성과 호출이 문제없이 잘 이루어 지므로 C소스코드 작성법의 설명을 덧붙였다.
PostgreSQL Extension function을 사용 하는 방법은 다음과 같다.
1. 사용하고 싶은 코드를 pg에서 제공하는 데이터형식을 이용하여 C언어로 작성한다.
2. C언어를 .so라이브러리로 빌드한다.
3. PG에 접속하여 2번에서 만든 라이브러리 경로를 주어 db에 함수를 생성한다.
1. 사용하고 싶은 코드를 pg에서 제공하는 데이터형식을 이용하여 C언어로 작성한다.
우선 PostgreSQL에서 제공하는 데이터 타입은 아래와 같다.
나의 경우, 문자열을 받아 받은 문자열을 가공하여, 가공한 문자열을 리턴하는 형식의 함수를 만들고자 했다.
필요한 return타입은 C에선 char*이지만, 아래 표에서 나타난대로 SQL Type에 varchar형식으로 출력하려면 VarChar*라는 데이터 타입을 사용해야 했고, 이 타입은 postgres.h파일에 정의되어있다고 표기되어있으므로 참고하여 소스코드를 작성하였다.
우선 작성한 소스코드를 통해 설명을 이어가보겠습니다.
#include "server/postgres.h"
#include "server/fmgr.h"
#include "utils/builtins.h" // $PG_HOME/include 위치에 존재하는 파일이므로, header파일의 위치에 맞게 조정해야함
PG_MODULE_MAGIC; //PG 매크로
PG_FUNCTION_INFO_V1(f_test_elephant); // PG에서 호출할 fucntion이름
Datum f_test_elephant(PG_FUNCTION_ARGS)
{
VarChar* input_dat = (VarChar *)PG_GETARG_VARCHAR_P(0);
int i = 0;
char *input_str = NULL;
char input_temp[1024+1] = {0,};
char output_str[1024+1] = {0,};
input_dat = (char *)VARDATA(input_dat);
strncpy(input_temp, input_str, strlen(input_str)+1);
for(i = 0; i < 1024; i++)
{
if( input_temp[i] == 0x00 )
input_temp[i] = 'A';
else
input_temp[i] += input_temp[i];
}
memcpy(output_str, input_temp, strlen(input_temp)+1);
PG_RETURN_VARCHAR_P(cstring_to_text(output_str)); // varchar 타입으로 return 하는 함수 (내부의 cstring_to_text()함수는 PG에서 지정한 구조체에 데이터를 담는 함수).
}
우선적으로 include 헤더파일들은 내가 사용하고자 하는 함수가 선언된 헤더파일만 포함하면되는데 위의 코드에서 사용된 함수들은 위 3가지 헤더파일에 선언되어있다.
파일을 작성하기 전 PG_MODULE_MAGIC; 이 매크로를 사용하는 것은 필수이다.
이 매크로의 내용은 fmgr.h에 아래의 사진처럼 선언되어있다.
이 매크로는 dlsym으로서의 기능을 사용하기 위해 필요하며, 백엔드에서 데이터가 아닌 기능에서만 작동하도록 보장된다고 한다. 이 매크로가 없으면 에러가 발생하니 실행 할 함수를 작성하기 앞서 반드시 포함해야한다.
실행하고자 하는 함수의 이름을 이 매크로 안에 넣어 실행 할 C함수 위에 다음 매크로를 사용해야한다.PG_FUNCTION_INFO_V1(f_test_elephant); 이 매크로는 fmgr.h에 아래와 같이 선언되어있다.
지정된 함수 이름과 연결된 정보 함수를 작성하는 매크로이다. PG에서 자동으로 PGDLEXPORT를 할 수 있도록 하고, 함수이름에 대한 외부 선언을 제공할 수 있도록 하는 매크로이다. C로 작성된 소스코드를 db 함수로 선언하여 사용할 것이기때문에 필요한 부분이다.
보면은 extern Datum funcname (PG_FUNCTION_ARGS)라고 작성되어있는데 우리는 그 형식에 맞게 함수를 써주면 된다. 때문에 C함수를 Datum f_test_elephant(PG_FUNCTION_ARGS)로 동일한 형태로 맞추어 작성해주었다.
Datum 은 postgres.h에 보면 Datum 타입은 내가 사용 할 VarChar 타입을 포함한 거의 모든 타입으로 변환이 가능한 것으로 확인할 수 있으며, 내부에서 사용한 char *형식을 Datum 형식으로 변환할 수 있는 함수를 제공하고있기 때문에 데이터 return에 용이했다.
인자값으로 들어온 데이터는 VarChar* input_dat = (VarChar *)PG_GETARG_VARCHAR_P(0); 를 통해 함수 내부 변수로 받을 수 있으며, 그 값은 input_dat = (char *)VARDATA(input_dat); 이 매크로를 통해 C 내부에서 우리가 잘 알고있는 데이터타입 형태로 변경하여 사용할 수 있다.
여기에서 주의 할 점은 VarChar의 선언부를 쫓아가다보면 구조체 형태인 것을 다음과 같이 확인할 수 있는데, 데이터를 직접 건드리지 말라고 쓰여있으므로 반드시 내부의 데이터를 건들일 때에는 PG에서 제공하는 매크로를 이용하여 값을 변경하거나 쓰도록 해야한다.
이제 db함수에서 넘겨준 파라미터를 C에서 사용할 수 있는 char*형태로 변환했으니 하고싶은 가공을 하고, 가공이 완료 된 데이터를 return할 차례만 남아있다.
PG에서 접근 할 C소스코드의 return형은 Datum 형태로 되어있으니 우리 역시 Datum 형태로 변환을 해 주어야 한다.
Datum 형태로 변환하는 것은 builtins.h에 함수로 정의되어있다.
char*형태를 text로 변환해주고, text라는 데이터타입도 역시 varlena 구조체이므로, VarChar 형태로 변환하는 것과 동일하다.(c.h에서 확인 가능)
그 후 변환 된 데이터를 PG_RETURN_VARCHAR_P 메크로를 이용하여 varchar 타입으로 return 해 주면 된다.
헤더파일을 보면은 다양한 변수형태를 제공하고 있으니 VarChar 타입이 아닌 다른 타입으로 return이 필요하신 분들도 참고하여 만들면 금방 습득이 가능 할 것이다.
위의 과정에서 캐스팅을 잘못하면 다음과 같은 에러가 발생하니 잘 확인해야한다.
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
2. C언어를 .so라이브러리로 빌드한다.
이제 1에서 열심히 작성한 코드를 라이브러리로 빌드하면 되는데, 우리는 소스코드를 작성할 때 Postgresql에서 제공하는 헤더파일이 굉장히 많이 필요했으므로, 헤더파일을 같은 위치에 옮겨서 빌드하던가, 그게 아니라면 빌드 옵션으로 헤더파일의 위치를 링크하여 빌드해주어야 빌드가 오류없이 완료된다.
나의 경우 메이크파일을 만들었지만, 메이크파일에는 환경적인 요소를 포함하여 만들기만 하면 되므로 혼란을 주지 않기 위해 헤더파일과 같은 위치에서 빌드 했을 경우의 예시를 들었다.
메이크 파일으로 빌드 할 사람들은 라이브러리 빌드에 필요한 아래의 옵션을 메이크파일에 넣어 빌드하면 된다.
cc -fPIC -c test_elephant.c
cc -shared -o test_elephant.so test_elephant.o
3. PG에 접속하여 2번에서 만든 라이브러리 경로를 주어 db에 함수를 생성한다.
-- 함수 생성부
REATE OR REPLACE FUNCTION f_test_elephant(varchar) RETURNS varchar
AS '/home/elephant/pgsql/include/server/test_elephant.so', 'f_test_elephant'
LANGUAGE C STRICT;
--함수 실행부
select f_test_elephant('test string');
오라클 보다는 C함수 작성이 어렵지만, 또 db내부에서 해야할 설정들은 비교적 간단하고 쉽다.
생각보다 어렵지 않으니 필요한 상황에 유용하게 사용하기를 바란다!
<참고>
https://postgresql.kr/docs/13/sql-createfunction.html
https://www.postgresql.org/docs/current/xfunc-c.html