7. 확장 SQL : 함수

함수를 정의하는 것은 새로운 형(type)을 정의하는 것중 일부분이다. 새로운 형(type)을 정의하지 않고 함수를 정의하는 것은 가능하지만, 그 역은 불가능 하다. 따라서 여기서는 새로운 형(type)을 추가하는 방법을 설명하기에 앞서 새로운 함수를 추가하는 방법부터 먼저 설명할 것이다.

이장에서 나오는 예제들은 funcs.sql 과 C-code/funcs.c 에서 찾을 수 있다.

7.1 질의어 (SQL) 함수

7.1.1 기본형 SQL 함수

가장 간단한 SQL 함수는 매개인자는 없고, int4 와 같은 기본형을 반환하는 것이 아닐까?

    CREATE FUNCTION one() RETURNS int4
         AS 'SELECT 1 as RESULT' LANGUAGE 'sql';
 
    SELECT one() AS answer;
 
 
    answer
    ------
         1

여기에서 대상 리스트를 RESULT 라는 이름으로 함수를 위해 정의하였으나, 함수를 불러오는 질의에서의 대상 리스트는 함수의 대상 리스트를 무효화한다. 따라서, 그 결과에는 one 대신 answer 이라는 이름의 라벨이 붙는다.

이제 매개인자로 기본형을 가지는 SQL 함수를 정의해보자. 아래의 예에서, 함수내 부에서 매개인자를 참조할 때는 $1, $2 와 같이 사용한다는 점에 주의하라.

    CREATE FUNCTION add_em(int4, int4) RETURNS int4
         AS 'SELECT $1 + $2;' LANGUAGE 'sql';
 
    SELECT add_em(1, 2) AS answer;
    
 
    answer
    ------
         3

7.1.2 복합형 SQL 함수

함수에서의 매개인자가 복합형 (EMP 처럼)일 경우에는, 원하는 (위에서 $1, $2 라고 했던 것처럼) 인자를 명시만 해서는 안되고 인자의 속성까지 명시해야 한다. 예를 들어, 사원의 급료를 두배로 계산하는 double_salary 함수를 살펴보자.

    CREATE FUNCTION double_salary(EMP) RETURNS int4
         AS 'SELECT $1.salary * 2 AS salary;' LANGUAGE 'sql';
 
    SELECT name, double_salary(EMP) AS dream
        FROM EMP
        WHERE EMP.dept = 'toy';
 
 
   name|dream
   ----+-----
   Sam | 2400

$1.salary 문법의 사용에 주의하자. 복합형을 반환하는 함수로 들어가기 전에, 먼저 속성과 관련된 함수식 표기법부터 알 아보자. 이러한 표기방식에는 attribute(class) 와 class.attribute 의 두가지가 있으며 서로 바꾸어 사용할 수 있다.

   --
   -- 이것은 다음의 사용과 동일하다:
   --    SELECT EMP.name AS youngster FROM EMP WHERE EMP.age < 30
   --
   SELECT name(EMP) AS youngster
   FROM EMP 
   WHERE age(EMP) < 30;
 
 
   youngster
   ---------
   Sam      

결과가 항상 이와 같지는 않다. 이 함수식 표기법은 하나의 인스턴스를 반환하는 함수를 사용할 때 중요하다. 이러한 작업은 함수내의 전체 인스턴스를 속성을 하나씩 끌어모음으로써 할 수 있다. 다음은 하나의 EMP 인스턴스를 반환하는 함수의 예이다.

   CREATE FUNCTION new_emp() RETURNS EMP
      AS 'SELECT \'None\'::text AS name,
         1000 AS salary,
                  25 AS age,
                  \'none\'::char16 AS dept;'
      LANGUAGE 'sql';

여기서는 각각의 속성을 상수값으로 지정하였으나, 어떠한 계산값이나 표현식도 이러한 상수값을 대체할 수 있다. 이와 같이 함수를 정의하는 것은 일종의 트릭일 것이다. 몇가지 주의해야할 중요 한 사항은 다음과 같다.

    SELECT name(new_emp()) AS nobody;

    nobody
    ------
    None

* 함수에서의 반환값에서 속성을 취할 때는 반드시 함수식 표기법을 사용해야 하는 데, 그 이유는 함수호출을 결합해서 사용할 때 파서는 점('.')이 붙은 다른 문법 을 이해하지 못하기 때문이다.

    SELECT new_emp().name AS nobody;
    WARN:parser: syntax error at or near "."

SQL 질의어에서 어떠한 명령의 조합도 함께 묶을 수 있으며 함수로 정의할 수 있다. SQL 로 작성되는 함수에서 select 질의와 마찬가지로 갱신(insert, update, delete 등) 명령을 포함할 수 있다. 그러나, 마지막 명령은 반드시 select 여야 하며, 함수의 returntype 으로 지정된 어떠한 값을 반환하여야 한다.

   CREATE FUNCTION clean_EMP () RETURNS int4
     AS 'DELETE FROM EMP WHERE EMP.salary <= 0;
         SELECT 1 AS ignore_this'
     LANGUAGE'sql';
 
   SELECT clean_EMP();
 
   x
   -
   1

7.2 프로그래밍 언어 함수

7.2.1 기본형 프로그래밍 언어 함수

POSTGRES 는 내부적으로 기본형을 메모리의 파편으로 인식한다. 하나의 형을 넘어 정의한 사용자 정의 함수는 POSTGRES 함수상에서 작동할 수 있는 방법을 정의하는 것이다. POSTGRES 는 함수를 저장하고 디스크에서 읽어올 뿐이며, 데이터 입력. 처리.출력을 위해 사용자 정의함수를 사용할 뿐이다.

기본형은 다음의 세가지 내부적인 형식중 하나를 가질 수 있다.

값에 의한 형(type)은 사용자의 컴퓨터가 다른 크기의 변수 형태를 제공한다 할지 라도, 그 길이는 1,2,4 바이트만 될 수 있다. POSTGRES 는 그자체로는 값에 의한 전달방식으로 정수형만 넘겨줄 수 있다. 새로운 형(type)을 정의할 때는 모든 아 키텍쳐에서 같은 크기(바이트수로)가 되어야 한다는 점에 주의해야 한다. 예를 들 어, long 형은 위험한데, 그 이유는 int 형이 4바이트인 대부분의 유닉스 머쉰상에 의 어떠한 머쉰에서는 4바이트이고 다른 머쉰에서는 8바이트가 될 수 있기 때문이 다 (대개의 PC 에서는 그렇지 않겠지만). 유닉스 머쉰상에서 int4는 다음과 같다.

    /* 4-byte integer, passwd by value */
    typedef int int4;

그외의 경우에, 고정길이 형은 참조에 의한 전달이 된다. 예를 들어, POSTGRES 의 char16 형은 다음과 같다.

    /* 16-byte structure, passed by reference */
    typedef struct {
        char data[16];
    } char16;

char16과 같은 형을 POSTGRES 로 넘겨주거나 받을 때에는 이 형에 대한 포인터만을 사용할 수 있다.

마지막으로, 모든 가변길이 데이터 형은 참조에 의한 전달이어야 한다. 아울러, 모든 가변길이 데이터 형은 정확히 4바이트의 길이를 나타내는 필드로 시작되어야 하며, 해당 데이터 형에 저장될 모든 데이터는 길이표시 필드 바로 다음의 메모리 위치상에 오도록 해야 한다. 길이 필드는 길이 필드 그 자체의 길이를 포함한, 구조체 길이의 합계이다. 'text' 데이터 형을 다음과 같이 정의할 수 있다.

    typedef struct {
        int4 length;
        char data[1];
    } text;

사실, data 필드는 가능한 모든 문자열을 저장하기에는 충분하지 않다 -- C 에서도 이러한 충분한 구조체를 선언하는 것은 불가능하다. 가변길이 데이터 형을 처리할 때는, 적절한 양의 메모리를 할당하고 길이 필드를 초기화해야 한다는 점에 유의 해야 한다. 예를 들어, text 구조체에 40바이트를 저장하려 할 경우, 다음과 같은 형태를 띄게 될 것이다.

    #include "postgres.h"
    #include "utils/palloc.h"
    ...
    char buffer[40]; /* our source data */
    ...
    text *destination = (text *)palloc(VARHDRSZ + 40);
    destination->length = VARHDRSZ + 40;
    memmove(destination->data, buffer, 40);
    ...

지금까지 기본형에서 사용가능한 구조를 모두 살펴보았다. 이제 실제 함수의 예를 조금 살펴보도록 하자. C-code/funcs.c 의 내용은 다음과 같다.

    #include <string.h>
    #include "postgres.h" /* for char16, etc. */
    #include "utils/palloc.h" /* for palloc */

    int add_one(int arg) { return(arg + 1); }

    char16 * concat16(char16 *arg1, char16 *arg2)
    {
        char16 *new_c16 = (char16 *) palloc(sizeof(char16));
        memset((void *) new_c16, 0, sizeof(char16));
        (void) strncpy(new_c16, arg1, 16);
        return (char16 *)(strncat(new_c16, arg2, 16));
    }

    text * copytext(text *t)
    {
        /* * VARSIZE is the total size of the struct in bytes. */
        text *new_t = (text *) palloc(VARSIZE(t));
        memset(new_t, 0, VARSIZE(t));
        VARSIZE(new_t) = VARSIZE(t);

        /* * VARDATA is a pointer to the data region of the struct. */
        memcpy( (void *) VARDATA(new_t), /* destination */
                (void *) VARDATA(t), /* source */
                VARSIZE(t)-VARHDRSZ); /* how many bytes */ return (new_t);
    }

OSF/1 시스템 상에서는 다음과 같이 타이핑할 수 있다.

    CREATE FUNCTION add_one(int4) RETURNS int4 AS '/usr/local/postgres95/tutorial/obj/funcs.so' LANGUAGE 'c';
    CREATE FUNCTION concat16(char16, char16) RETURNS char16 AS '/usr/local/postgres95/tutorial/obj/funcs.so' LANGUAGE 'c';
    CREATE FUNCTION copytext(text) RETURNS text AS '/usr/local/postgres95/tutorial/obj/funcs.so' LANGUAGE 'c';

다른 시스템에서는, 그것이 공유 라이브러리라는 것을 나타내기 위해 아마도 파일이름의 끝이 .sl 이 되도록 해야 할 것이다

7.2.2 복합형 프로그래밍 언어 함수

복합형은 C 의 구조체처럼 고정되어 있지는 않다. 복합형의 인스턴스는 널 필드 를 포함할 수 있다. 아울러, 계층적으로 상속된 복합형은 계층적으로 같은 상속을 받은 다른 멤버와는 다른 필드를 가질 수 있다. 따라서, POSTGRES 는 C 로부터 엑세스 하는 복합형 필드를 위해 프로시져 인터페이스를 제공한다.

POSTGRES 가 인스턴스의 집합을 처리하는 것처럼, 각각의 인스턴스는 불투명한 TUPLE 형 구조체로 함수에 넘겨질 것이다.

어떠한 질의를 처리하기 위해 함수를 사용하려 한다고 가정해보자.

    SELECT name, c_overpaid(EMP, 1500) AS overpaid FROM EMP
    WHERE name = 'Bill' or name = 'Sam';

위의 질의에 나오는 c_overpaid 는 다음과 같이 정의할 수 있다.

    #include "postgres.h" /* for char16, etc. */
    #include "libpq-fe.h" /* for TUPLE */

    bool c_overpaid(TUPLE t, /* the current instance of EMP */ int4 limit)
    {
        bool isnull = false; int4 salary;
        salary = (int4) GetAttributeByName(t, "salary", &isnull);
        if (isnull) return (false);
        return (salary > limit);
    }

GetAttributeByName 은 POSTGRES 시스템 함수로서 현재 인스턴스의 속성을 반환한 다. 여기에는 세개의 매개인자가 있는 데, 그각각은 함수로 넘겨진 TUPLE 형 매개인자, 얻기를 바라는 속성의 이름, 해당 속성이 널인지를 나타내는 반환값을 저장하는 매개인자이다. GetAttributeByName 은 아마도 데이터를 정렬할 것이기에 반환값을 원하는 형으로 변환 할 수 있다. 예를 들면, char16 형인 속성 name 이 있다면, GetAttributeByName 호출은 다음과 같을 것이다.

    char *str; ... str = (char *) GetAttributeByName(t, "name", &isnull)

다음의 질의는 POSTGRES 에게 c_overpaid 함수를 인식시키기 위한 것이다.

    CREATE FUNCTION c_overpaid(EMP, int4) RETURNS bool
    AS '/usr/local/postgres95/tutorial/obj/funcs.so' LANGUAGE 'c';

C 함수내부에서부터 새로운 인스턴스를 생성하거나 존재하는 인스턴스를 변경할 수도 있지만, 이 메뉴얼에서 논의하기에는 너무 복잡하다.

7.3 몇가지 주의사항

이제 좀 더 까다로운 프로그래밍 언어 함수를 작성하는 임무로 돌아와보자. 경고하지만 이 내용은 여러분을 프로그래머로 만들기 위한 것이 아니다. 여러분들 은 POSTGRES 로 실제로 사용할 함수를 C 로 작성하려고 하기에 앞서 C (포인터의 사용, malloc 메모리 관리 등을 포함)를 제대로 이해하고 있어야 한다.

C 가 아닌 다른 프로그래밍 언어를 사용하여 작성한 함수를 POSTGRES 로 적재할 수 있긴 하지만, 이 작업은 종종 까다롭다. 왜냐하면 포트란이나 파스칼과 같은 다른 언어는 종종 "호출 관습" 이 C 와는 다르기 때문이다. 이점은, 다른 언어는 같은 방법으로 함수사이에 매개인자를 전달하거나 값을 반환할 수 없다는 것이다. 이러한 이유로, 여러분의 프로그래밍 언어 함수는 C 로 작성되었다고 가정한다.

C 로 함수를 작성할 때의 기본적인 규칙은 다음과 같다.

(1) POSTGRES 에 있어서 대부분의 헤더파일은 /usr/local/postgres95/include 에 설치되어 있어야 한다(그림 2에서 보듯이). cc 명령행에서 -I/usr/local/postgres95/include 를 명시하여 헤더파일을 항상 포함하여야 한다. 가끔, 당신이 찾는 헤더파일이 서버 소스 중에 있을 수도 있다 (우리가 게을러서 설치에 빠뜨린 헤더파일은 여러분들이 직접 include 에 설치할 필요성이 있다). 이러한 경우에 다음 중 하나이상을 추가할 수도 있다.

    -I/usr/local/postgres95/src/backend -I/usr/local/postgres95/src/backend/include
    -I/usr/local/postgres95/src/backend/port/<PORTNAME> -I/usr/local/postgres95/src/backend/obj

(여기에서 <PORTNAME> 은 alpha 나 sparc 와 같은 포트 명칭이다. )

(2) 메모리 할당을 할 때, C 라이브러리의 malloc 와 free 루틴 대신 POSTGRES 의 palloc 와 pfree 루틴을 사용한다. palloc 에 의해 할당된 메모리는 매개 트랜 잭션의 끝부분에서 자동적으로 메모리를 자유롭게 하며, 메모리 유출을 막는다.

(3) 해당 구조체는 항상 memset 또는 bzero 를 사용하여 0으로 초기화하는 것이 좋 다. (해쉬 엑세스 메쏘드, 해쉬 결합, 정렬 알고리즘 등과 같은) 여러 루틴은 구조체에 있는 로우(raw) 비트의 작용을 추정한다. 구조체의 필드를 모 두 초기화한다 할지라도, 쓰레기 값을 포함하는, 정렬시에 발생하는 채워넣기 (구조체에서의 홀)바이트가 있을 수 있다.

(4) 대개의 내장 POSTGRES 데이터 형은 postgres.h 에 포함되어 선언되어 있으므로, 보통 이 파일을 포함하는 것이 좋다.

(5) POSTGRES 에 동적으로 적재할 목적코드를 컴파일하고 적재할 시에는특별한 플래그를 필요로한다. 특정한 운영체제 상에서 이러한 작업을 하는 방법에 대 한 자세한 정보는 부록 A 를 참조하라.