2. 외부 프로시저 사용법#
이 장에서는 Altibase에서 외부 프로시저를 사용하는 방법을 설명한다.
먼저, 외부 프로시저를 위해 지원되는 C/C++ 자료형과 데이터베이스 객체에 대해 알아보고, Altibase 서버가 인식할 수 있는 동적 라이브러리를 작성하고 생성하는 방법을 설명한다.
자료형과 데이터베이스 객체#
이 절은 외부 프로시저 작성을 위해 지원하는 자료형과 외부 프로시저 사용을 위해 제공하는 외부 라이브러리 객체와 외부 프로시저 객체를 소개한다.
자료형#
아래는 외부 프로시저 객체 생성 시 인자에 사용할 수 있는 PSM 자료형과 사용자 정의 C/C++ 함수의 인자에 사용할 수 있는 자료형을 연결한 표이다. 인자의 입/출력 모드에 따라 사용자 정의 C/C++ 함수에서는 포인터 형을 써야 할 수 있으므로 주의해서 살펴보기 바란다.
PSM 자료형 | IN | INOUT/OUT | RETURN | 비고 |
---|---|---|---|---|
BIGINT | long long | long long * | long long | |
BOOLEAN | char | char * | char | BOOLEAN의 경우에는 아래 두 가지 값을 허용한다. 0: FALSE 1: TRUE |
SMALLINT | int | int * | int | |
INTEGER | ||||
REAL | float | float * | float | |
DOUBLE | double | double * | double | |
CHAR | char * | char * | char * | |
VARCHAR | ||||
NCHAR | ||||
NVARCHAR | ||||
BYTE | ||||
VARBYTE | ||||
NUMERIC | double | double * | double | Precision이 높은 인자는 double 형으로 변환하는 과정에서 데이터 손실이 발생할 수 있다. |
DECIMAL | ||||
NUMBER | ||||
FLOAT | ||||
DATE | SQL_TIMESTAMP_STRUCT | SQL_TIMESTAMP_STRUCT * | SQL_TIMESTAMP_STRUCT | |
INTERVAL | SQL_TIMESTAMP_STRUCT | SQL_TIMESTAMP_STRUCT * | SQL_TIMESTAMP_STRUCT |
외부 라이브러리 객체#
사용자 정의 함수, 즉 외부 프로시저를 포함하는 동적 라이브러리 파일을 Altibase 서버가 식별할 수 있도록 해 주어야 한다. 이를 위해 Altibase는 외부 라이브러리 객체라는 데이터베이스 객체를 제공하며, 객체 관리를 위한 SQL구문도 함께 제공한다.
외부 프로시저를 저장 프로시저처럼 사용하기 위해 사용자는 먼저 외부 라이브러리 객체를 생성해야 하며, 관련된 동적 라이브러리 파일을 $ALTIBASE_HOME/lib 디렉토리에 두어야 한다. 동적 라이브러리 파일 없이 외부 프로시저 객체를 생성하는 것이 가능하지만, 외부 프로시저를 실행할 때에는 반드시 관련 동적 라이브러리 파일이 존재해야 한다.
외부 프로시저/함수 객체#
Altibase는 외부 프로시저를 등록한 저장 프로시저를 일반적인 저장 프로시저와 구별해서 외부 프로시저 객체라고 한다. 외부 프로시저 객체는 사용자 정의 함수와 일대일로 대응하는 데이터베이스 객체이다. 외부 프로시저인 사용자 정의 함수는 외부 프로시저 객체를 통해 실행된다. 외부 프로시저 객체를 생성할 때에는 관련된 라이브러리 객체와 사용자 정의 함수에 대한 모든 정보를 명시해야 한다.
Altibase는 외부 프로시저 객체의 관리를 위한 SQL구문을 제공한다.
기본 사용법#
외부 프로시저를 사용하는 일반적인 단계는 다음과 같다.
-
사용자 정의 함수 작성
-
진입 함수 작성
-
외부 라이브러리 객체 생성
-
외부 프로시저/함수 객체 생성
-
동적 라이브러리 생성
-
외부 프로시저 호출
이 절에서는 위의 각 단계에 대해서 예제와 함께 설명한다.
사용자 정의 함수 작성#
사용자는 일반적인 C 또는 C++ 함수를 만드는 것과 동일한 방식으로 외부 프로시저용 C/C++ 함수를 작성할 수 있다.
이 절에서는 아래와 같은 인자 유형과 반환 유형을 갖는 저장 프로시저와 함수에 대응하는 사용자 함수를 각각 작성해 보겠다.
procedure str_uppercase_proc( a1 in char(30), a2 out char(30) )
function str_uppercase_func_int( a1 in char(30), a2 out char(30) ) return int
function str_uppercase_func_char( a1 in char(30), a2 out char(30) ) return
char(30)
extern "C" void str_uppercase( char\* str1, long long str1_len, char\* str2 );
여기에서 두 번째 인자는 첫 번째 인자에 입력될 문자열의 길이를 사용자 함수로 전달하기 위해 사용된다.
str_uppercase_func_int 및 str_uppercase_func_char에 대응하는 사용자 정의 C/C++ 함수 프로토타입은 아래와 같다.
extern "C" int str_uppercase_count( char* str1, long long str1_len, char* str2 );
extern "C" char* str_uppercase_return( char* str1, long long str1_len, char* str2 );
void str_uppercase(char *str1, long long str1_len, char * str2)
{
for( int i=0; i < str1_len; i++ )
{
str2[i] = toupper(str1[i]);
}
}
int str_uppercase_count(char *str1, long long str1_len, char * str2)
{
int capCount = 0;
int i = 0;
while( i < str1_len )
{
if( str1[i] >= 'A' && str1[i] <= 'Z' )
{
capCount++;
}
i++;
}
return capCount;
}
char* str_uppercase_return(char *str1, long long str1_len, char * str2)
{
for( int i=0; i < str1_len; i++ )
{
str2[i] = toupper(str1[i]);
}
return str2;
}
주의: 사용자 정의 함수 선언부에 반드시 extern "C" 키워드를 써야 한다.
진입 함수 작성#
사용자 정의 함수 외에도, 사용자 정의 함수를 실행할 정형화된 함수가 필요하다. 이 함수를 진입 함수(Entry Function)라고 한다.
에이전트 프로세스가 진입 함수를 호출하면 진입 함수가 사용자 정의 함수를 호출하고 그 결과를 반환한다. 따라서 동적 라이브러리 내에 진입 함수가 반드시 있어야 한다.
진입 함수 내부에는 실제 사용자 정의 함수를 호출하는 루틴이 포함되어야 한다.
아래는 위의 str_uppercase, str_uppercase_count와 str_uppercase_return 사용자 정의 함수를 호출하는 진입 함수의 예제이다.
extern "C" void entryfunction(char* func_name, int arg_count, void ** args, void ** returnArg);
void entryfunction(char* func_name, int arg_count, void ** args, void ** returnArg)
{
// 사용자 정의 부분
if(strcmp(func_name, "str_uppercase") == 0)
{
str_uppercase((char*)args[0], *((long long *)args[1]), (char*)args[2]);
}
else if(strcmp(func_name, "str_uppercase_count") == 0)
{
if( *returnArg != NULL ) // int 타입 반환
{
**(int**)returnArg = str_uppercase_count ((char*)args[0], *((long long*)args[1]), (char*)args[2]);
}
}
else if(strcmp(func_name, "str_uppercase_return") == 0)
{
if( returnArg != NULL ) // char* 타입 반환
{
*(char**)returnArg = str_uppercase_return ((char*)args[0], *((long long*)args[1]), (char*)args[2]);
}
}
}
-
func_name: 함수의 이름을 나타내는 문자열
-
arg_count: 인자의 개수
-
args: 인자들의 배열. 외부 프로시저에서 명시한 순서를 따라야 한다. 인자의 입출력 모드와 PSM 자료형에 따라 각각 맵핑되는 C 자료형이 다르므로 주의해야 한다.
-
returnArg: 반환 값을 위한 인자. 반환 값이 없는 외부 프로시저의 경우에는 이 인자에 항상 NULL이 반환되며, 외부 함수의 경우에는 반환 값을 가리키는 포인터가 반환된다.
주의 사항#
동적 라이브러리 내에서 진입 함수의 이름 entryfunction과 인자의 자료형은 사용자가 임의로 변경해서는 안 된다.
한 개의 라이브러리 내에 사용자 정의 함수가 두 개 이상 포함된 경우, 하나의 진입 함수 내에 사용자 정의 함수를 각각 호출하도록 소스 코드를 작성해야 한다.
외부 함수가 아닌 외부 프로시저의 사용자 정의 함수를 호출한 후 반환 값을 returnArg 변수에 대입하면 에이전트 프로세스가 비정상 종료할 수 있다. 즉, 외부 함수의 경우에는 returnArg가 NULL이 아닌지 반드시 검사한 후 함수 반환 타입에 맞게 타입 캐스팅하여 반환해야 한다.
사용자 정의 함수와 마찬가지로 진입 함수 선언부에도 반드시 extern "C" 키워드를 써야 한다.
위 예제의 사용자 정의 함수를 처리하는 부분에서 반환 값을 대입할 returnArg 변수(진입 함수의 마지막 인자)의 NULL 여부를 검사하는 코드가 함수 반환 타입에 따라 다른 점을 주의해서 살펴보기 바란다.
if( *returnArg != NULL ) // int 타입을 반환하는 경우 ... if( returnArg != NULL ) // char* 타입을 반환하는 경우
반환 값의 타입 캐스팅#
외부 함수에 대응하는 사용자 정의 함수를 호출한 후, 함수의 반환형에 따라 적절한 타입으로 캐스팅이 필요하다.
반환 타입 | 코드상의 타입 캐스팅 |
---|---|
char | **(char**)returnArg = ... |
int | **( int**)returnArg = ... |
long long | **( long long**)returnArg = ... |
float | **( float**)returnArg = ... |
double | **( double**)returnArg = ... |
char * | *(char**)returnArg = ... |
SQL_TIMESTAMP_STRUCT | **( SQL_TIMESTAMP_STRUCT**)returnArg = ... |
외부 라이브러리 객체 생성#
Altibase 서버가 동적 라이브러리 파일을 식별할 수 있도록 데이터베이스에 라이브러리 객체를 생성해야 한다. 예를 들어, 사용자가 shlib.so라는 이름의 동적 라이브러리 파일을 사용하고자 하는 경우, 데이터베이스에 아래와 같이 외부 라이브러리 객체를 생성할 수 있다.
CREATE OR REPLACE LIBRARY lib1 AS 'shlib.so';
이제 Altibase는 shlib.so 동적 라이브러리 파일을 lib1이라는 외부 라이브러리 객체로 식별한다.
외부 프로시저/함수 객체 생성#
외부 프로시저(사용자 정의 C/C++ 함수)를 실행하기 위해서는 먼저 데이터베이스에 사용자 정의 함수를 등록하는 외부 프로시저 객체를 생성해야 한다.
아래와 같은 SQL 구문으로 외부 프로시저 객체를 생성할 수 있다.
create or replace procedure str_uppercase_proc( a1 in char(30), a2 out char(30) )
as
language c
library lib1
name "str_uppercase"
parameters( a1, a1 LENGTH, a2 )
;
/
create or replace function str_uppercase_func_int( a1 in char(30), a2 out char(30) ) return int
as
language c
library lib1
name "str_uppercase_count"
parameters( a1, a1 LENGTH, a2 )
;
/
create or replace function str_uppercase_func_char( a1 in char(30), a2 out char(30) ) return char(30)
as
language c
library lib1
name "str_uppercase_return"
parameters( a1, a1 LENGTH, a2 )
;
/
동적 라이브러리 생성#
한 개 이상의 사용자 정의 함수와 진입 함수가 작성된 소스 파일이 준비되면, 컴파일 해서 동적 라이브러리 파일을 만들 수 있다. 동적 라이브러리 파일 확장자의 형태는 *.so이다.
아래 예제는 유닉스 계열에서 소스 파일을 컴파일 해서 동적 라이브러리를 만드는 것을 보여준다.
$ g++ -g -fPIC -shared -o shlib.so extproc.cpp
사용자 정의 함수들과 진입 함수가 여러 파일로 이루어져 있다면, 아래와 같이 각 소스 파일을 컴파일 한 후 링크하면 된다.
$ g++ -fPIC -c extproc.cpp
$ g++ -fPIC -c entry.cpp
$ g++ -shared -o shlib.so entry.o extproc.o
생성된 동적 라이브러리 파일은 Altibase 서버가 인식할 수 있는 위치로 이동해야 한다. 그 위치는 $ALTIBASE_HOME/lib이며, 이 위치는 사용자가 임의로 변경할 수 없다.
$ mv shlib.so $ALTIBASE_HOME/lib/
외부 프로시저 호출#
다음은 iSQL에서 외부 프로시저를 실행하는 것을 보여주는 예제이다.
var var1 char(30);
var var2 char(30);
exec :var1 := 'hello world';
exec str_uppercase_proc( :var1, :var2 );
iSQL> print var;
[ HOST VARIABLE ]
-------------------------------------------------------
NAME TYPE VALUE
-------------------------------------------------------
VAR1 CHAR(30) hello world
VAR2 CHAR(30) HELLO WORLD
extproc.cpp#
다음은 위의 절에서 예로 든 사용자 정의 함수를 작성한 소스 파일 extproc.cpp이다.
#include <string.h>
#include <ctype.h>
extern "C" void str_uppercase( char* str1, long long str1_len, char* str2 );
extern "C" int str_uppercase_count( char* str1, long long str1_len, char* str2 );
extern "C" char* str_uppercase_return( char* str1, long long str1_len, char* str2 );
extern "C" void entryfunction(char* func_name, int arg_count, void ** args, void ** returnArg);
void entryfunction(char* func_name, int arg_count, void ** args, void ** returnArg)
{
if(strcmp(func_name, "str_uppercase") == 0)
{
str_uppercase((char*)args[0], *((long long*)args[1]), (char*)args[2]);
}
else if(strcmp(func_name, "str_uppercase_count") == 0)
{
if( *returnArg != NULL ) // int 타입을 반환
{
**(int**)returnArg = str_uppercase_count ((char*)args[0], *((long long*)args[1]), (char*)args[2]);
}
}
else if(strcmp(func_name, "str_uppercase_return") == 0)
{
if( returnArg != NULL ) // char* 타입을 반환
{
*(char**)returnArg = str_uppercase_return ((char*)args[0], *((long long*)args[1]), (char*)args[2]);
}
}
}
void str_uppercase(char *str1, long long str1_len, char * str2)
{
for( int i=0; i < str1_len; i++ )
{
str2[i] = toupper(str1[i]);
}
}
int str_uppercase_count(char *str1, long long str1_len, char * str2)
{
int capCount = 0;
int i = 0;
while( i < str1_len )
{
if( str1[i] >= 'A' && str1[i] <= 'Z' )
{
capCount++;
}
i++;
}
return capCount;
}
char* str_uppercase_return(char *str1, long long str1_len, char * str2)
{
for( int i=0; i < str1_len; i++ )
{
str2[i] = toupper(str1[i]);
}
return str2;
}
관련 메타 테이블과 성능 뷰#
메타 테이블
-
SYS_LIBRARIES_
현재 데이터베이스에 생성된 외부 라이브러리 객체에 대한 정보를 확인할 수 있다.
성능 뷰
-
V$EXTPROC_AGENT
외부 프로시저 실행을 위해 현재 생성되어 있는 에이전트 프로세스(agent process)의 정보를 확인할 수 있다.
-
V$LIBRARY
현재 데이터베이스에서 직접 로드한 동적 라이브러리에 대한 정보를 확인할 수 있다.
-
V$PROCINFO
현재 데이터베이스에서 외부 프로시저의 모드를 확인할 수 있다.
각 메타 테이블과 성능 뷰에 대한 상세한 설명은 General Reference를 참고하도록 한다.
관련 프로퍼티#
아래는 외부 프로시저를 위한 에이전트의 동작에 관련된 프로퍼티이다. 에이전트 프로세스를 생성하지 않는 internal mode는 영향을 받지 않는다.
-
EXTPROC_AGENT_CONNECT_TIMEOUT
-
EXTPROC_AGENT_CALL_RETRY_COUNT
-
EXTPROC_AGENT_IDLE_TIMEOUT
-
EXTPROC_AGENT_SOCKET_FILEPATH
각 프로퍼티에 대한 상세한 설명은 General Reference를 참고하도록 한다.