1. PostgreSQL의 C 인터페이스 - libpq 지금까지는 PostgreSQL의 SQL특징을 살펴보았다. 이제부터는 SQL 외의 기능들을 중점으로 살펴보도록 하겠다. 그 첫 번째로서 PostgreSQL의 C 인터페이스인 libpq에 대해서 알아보도록 하자. libpq는 말그대로 PostgreSQL의 데이터베이스를 다루는데 사용되는 C 라이브러리이다. 사실 사용자 프로그램인 psql도 libpq를 사용하여 작성한 어플리케이션의 일종이다. 만들기에 따라서 psql 보다 더 뛰어난 사용자 프로그램을 얼마든지 만들 수 있다. PostgreSQL을 표준으로 설치하였다면, libpq는 /usr/local/pgsql/lib안에 모여있을 것이다. 이 디렉토리 안의 libpq.a는 정적 라이브러리이고 libpq.so.1은 동적 라이브러리이다. 설치 시에 /ect/ld.so.conf 파일에 /usr/local/pgsql/lib를 추가하고 ldconfig를 수행한 경험이 있을 것이다. 이것은 PostgreSQL 응용 프로그램 수행에 필요한 libpq 동적 라이브러리를 자동적으로 링크하기 위해 필요한 작업이었다. 조금 벗어나는 이야기지만 PHP/FI에서 PostgreSQL을 사용하도록 설정하였는데, httpd를 띄울 때 libpq.so를 찾지 못하겠다는 메시지가 나온다면 libpq.so.1을 libpq.so로 심볼릭 링크하고 /etc/ld.so.conf 파일에 /usr/local/pgsql/lib을 추가한 다음, ldconfig를 수행하면 된다. libpq에 대한 온라인 설명은 'man libpq'로 찾아볼 수 있고, 예제 소스 파일은 PostgreSQL 소스 파일의 압축을 푼 디렉토리를 기준으로 볼 때, src/test/examples 디렉토리에 있다. 이제 본론으로 들어가보자.
2. libpq 의 함수들 libpq는 PostgreSQL의 어플리케이션 프로그래밍 인터페이스이다. libpq는 클라이언트 프로그램이 PostgreSQL 백엔드 서버에게 질의를 전달하고 결과를 회수하는 역할을 하는 라이브러리이다. libpq를 사용하는 프론트엔드 어플리케이션은 libpq-fe.h 헤더 파일을 포함하고 libpq 라이브러리와 링크되어야 한다는 점을 기억하자. 먼저 libpq에 포함되어 있는 라이브러리 함수들을 살펴보도록 한다.
1) 데이터베이스 초기화와 제어와 관련한 환경변수 Linux 환경의 다른 어플리케이션들과 마찬가지로 libpq에서도 초기화와 어플리케이션의 행동을 제어하기 위하여 환경변수를 사용한다. 다음의 환경변수의 값이 libpq에서 기본값으로 사용된다. PGHOST 기본 서버명을 지정한다. PGOPTIONS 벡엔드 서버를 위한 추가적인 실시간 옵션을 설정한다. PGPORT 벡엔드 서버와 통신할 기본 포트를 지정한다. PGTTY 벡엔드 서버가 출력하는 디버깅 메시지를 처리할 파일이나 tty를 지정한다. PGDATABASE 기본 데이터베이스 명을 지정한다. PGREALM Kerberos 인증 시스템이 사용될 때에만 설정한다. |
2) 데이터베이스 접속 함수 다음의 함수들은 데이터베이스 접속과 관련된 것들이다. PQsetdb 서버와 새로운 연결을 만들어 준다. PGconn *PQsetdb(char *pghost, char *pgport, char *pgoptions, char *pgtty, char *dbName); |
인자가 NULL이면, 해당하는 환경변수를 검사하고, 환경변수가 설정되지 않았다면 내부 기본 설정값을 사용한다. 이 함수는 항상 유효한 PGconn 포인터를 반환하는데, PQstatus를 사용하여 질의를 서버로 보내기 전에 연결이 확실히 성립되었는지를 검사할 수 있다. libpq 사용자는 PGconn을 관리하는데 유의해야 한다. PGconn 구조체는 미래에 변경될 수도 있기 때문에 직접 구조체의 필드를 참조하는 것은 피하는게 좋다. 예) PGconn *conn; /* 192.168.1.2 호스트의 5432 포트를 통하여 web 데이터베이스에 접속한다. */ PQsetdb("192.168.1.2", "5432", NULL, NULL, "web"); |
PQport 연결된 포트 번호를 반환한다. char *PQport(PGconn *conn) |
PQtty 연결된 tty를 반환한다. char *PQtty(PGconn *conn) |
PQreset 서버와의 접속 포트를 리셋한다. 즉, 서버에 연결된 IPC 소켓을 닫고 동일한 서버에 접속을 새롭게 시도한다. void PQreset(PGconn *conn) |
3) 질의 실행 함수 PQexec 질의를 서버에 전달하는 역할을 한다. 질의가 성공적으로 수행되면 PGresult 포인터를 돌려주고, 그렇지 않으면 NULL을 돌려준다. NULL이 반환되면, PQerrorMessage를 사용하여 해당 에러에 대한 좀 더 자세한 정보를 얻을 수 있다. PGresult *PQexec(PGconn *conn, char *query); |
PGresult 구조체에는 서버가 반환한 질의 결과가 들어있다. 프로그래머는 PGresult를 조심스럽게 관리해야 할 필요성이 있다. 질의 결과를 회수하는 데 사용되는 접근 함수를 아래에 설명한다. PGresult 구조체는 PGconn 구조체와 마찬가지로 앞으로 언제든지 변할 가능성이 있기 때문에 직접 구조체의 필드를 참조하는 것은 피하기 바란다.
PQresultStatus 질의 결과 상태를 알려준다. ExecStatusType PQresultStatus(PGresult *res); |
반환값인 ExecStatusType은 다음 중 하나가 될 수 있다. PGRES_EMPTY_QUERY : 질의가 비어 있는 경우 PGRES_COMMEND_OK : 값을 반환하지 않는 질의 명령인 경우 PGRES_TUPLES_OK : 질의가 성공적으로 수행되어서 튜플을 반환한 경우 PGRES_BAD_RESPONSE : 서버로부터 기대하지 않은 응답을 받은 경우 PGRES_NONFATAL_ERROR : 치명적이지 않은 에러가 발생한 경우 PGRES_FATAL_ERROR : 치명적인 에러가 발생한 경우 PGRES_COPY_OUT PGRES_COPY_IN |
결과의 상태값이 PGRES_TUPLES_OK이면 반환된 튜플을 회수하기 위해 다음의 함수들을 사용할 수 있다.
PQntuples 질의 결과의 튜플(인스턴스 또는 레코드, 로우)의 개수를 반환한다. int PQntuples(PGresult *res); |
PQnfields 필드의 개수를 반환한다. int PQnfields(PGresult *res); |
PQftype 저장하는 필드 인덱스의 필드 타입을 돌려준다. 반환되는 정수값은 내부적으로 정의되어 있는 text, int4 등을 나타내는 값이다. Oid PQftype(PGresult *res, int field_num); |
PQgetvalue 필드의 이름을 지정하면 그 필드의 값을 돌려준다. PQgetvalue에서 반환되는 값은 필드의 값을 널로 끝나는 아스키 문자열로 변환한 값이다. 질의가 바이너리(BINARY) 커서일 경우에 반환되는 값은 서버의 내부적인 포맷의 바이너리 타입이다. 이 경우에 해당 데이터를 올바른 C타입으로 변환해야 한다. PQgetvalue가 반환하는 값은 PGresult 구조체의 해당 필드에 대한 포인터이므로 PGresult를 해제하고 난 다음에도 사용하려면 그 값을 복사해 둬야 한다. char *PQgetvalue(PGresult *res, int tup_num, int field_num); |
PQoidStatus 마지막으로 수행한 질의가 INSERT 명령일 때, 삽입된 튜플의 객체 아이디를 문자열로 돌려준다. 그 외에는 빈 문자열을 돌려준다. char *PQoidStatus(PGresult *res); |
PQdisplayTuples 임의의 모든 레코드를 출력한다. 선택적으로 타이틀격인 속성 이름의 출력여부와 출력 스트림을 지정할 수 있다. 대표적으로 psql이 테이블의 내용을 출력하기 위해 PQdisplayTuples 함수를 사용한다. void PQprintTuples( PGresult *res, FILE *fout, /* 출력결과를 보낼 파일 스트림 */ int fillAlign, /*필드를 정렬하기 위해 빈부분을 공백으로 채움여부*/ char *fieldSep, /* 필드 구분자로 사용할 문자열, 일반적으로 '|'를 사용 */ int printHeader, /* 헤더의 출력 여부 */ int quit ); |
PQclear PGresult와 관련된 내용을 해제하는 역할을 한다. 질의 결과가 더 이상 필요 없을 시에는 반드시 해제하여야 한다. 이렇게 하지 않으면 프론트엔드 응용프로그램에서 메모리가 유출되는 결과를 낳는다.
4) COPY 질의와 관련된 함수 PQgetline 이 함수는 뉴라인으로 끝나는 문자열을 읽어서 string 버퍼에 void PQclear(PQresult *res); |
저장한다. fgets와 비슷하게, length-1 문자를 string으로 복사하고, 마지막의 뉴라인 문자를 널 문자로 변환한다는 것이 gets와는 다른 점이다.
int PQgetlline(PQconn *conn, char *string, int length); |
PQgetline은 읽어 들이는 도중에 EOF를 만나면 EOF를 반환하고, 전체 라인을 읽어들였다면 0을, 뉴라인을 읽기도 전에 버퍼가 차버리면 1을 반환한다. 어플리케이션에서는 하나의 뉴라인이 "."로 입력되는 지를 검사하여야 한다. 이 문자는 서버가 copy 명령의 결과 전송을 종료한다는 것을 뜻한다. 그리고 length-1 보다 큰 길이의 라인을 읽어들일 수도 있으므로 PQgetline의 반환값을 체크하여야 한다.
PQputline 이 함수는 널로 끝나는 string을 서버에 전송한다. 어플리케이션에서는 마지막 문자 "."를 데이터 전송이 완료되었다는 것을 서버에게 정확히 알리기 위해 보내야 한다. void PQputline(PGconn *conn, char *string); |
PQendcopy 서버가 copy를 끝낼 때까지 응용 프로그램이 기다리도록 한다. 이 함수는 PQputline을 사용하여 서버에게 마지막 문자열을 보냈을 경우나 PQgetline을 사용하여 서버에게서 마지막 문자열을 회수하였을 경우에 사용한다. 이 함수의 반환값에 따라, 서버는 다음 질의를 받아들일 준비를 한다. 성공적으로 수행되었다면 0을, 그 외에는 0이 아닌 값을 반환한다. int PQendcopy(PGconn *conn); |
참고로 PostgreSQL에서는 질의 버퍼가 8192 바이트 길이이므로, 8K를 넘는 질의의 뒷부분은 잘려나가므로 주의하기 바란다. 물론 그럴 경우는 없겠지만... 예) PQexec(conn, "create table foo (a int4, b char16, d float8)"); PQexec(conn, "copy foo from stdin"); /* 여기에서 <TAB>은 실제로 TAB키를 입력함을 이야기한다. */ PQputline(conn, "3<TAB>hello world<TAB>4.5\n"); PQputline(conn, "4<TAB>goodbye world<TAB>7.11\n"); PQputline(conn, ".\n"); PQendcopy(conn); |
3. libpq의 활용 1) 예제 프로그램 - testlibpq.c 여기에서는 먼저 PostgreSQL에서 제공하는 괜찮은 예제 소스를 먼저 살펴본다. 이 파일은 src/test/examples/testlibpq.c 파일이다. 먼저 컴파일을 하여 실행을 해보자. $ make $ ./testlibpq datname datdba datpath template1 501 template1 ddoch 501 ddoch test3 501 test3 mydb 501 mydb web 506 web test 501 test |
이 프로그램은 임시 데이터베이스로 사용되는 template1 데이터베이스에 접속하여 시스템 카탈로그의 일종인 pg_database 클래스에서 데이터베이스 목록을 축출하여 출력하고 있다. 다음은 소스 프로그램이다. 자세한 주석을 붙여놓았다. /* * testlibpq.c * PostgreSQL 프론트엔드 라이브러리인 LIBPQ를 사용한 테스트 프로그램 * * */ #include <stdio.h> #include "libpq-fe.h" /* LIBPQ를 사용하는 프로그램에서 꼭 포함해야 한다. */ static void exit_nicely(PGconn *conn) { PQfinish(conn); exit(1); } int main() { char *pghost, *pgport, *pgoptions, *pgtty; char *dbName; int nFields; /* 필드의 갯수를 저장할 변수 */ int i,j; #ifdef DEBUG FILE *debug; /* 디버깅을 위한 파일 스트림 */ #endif /* DEBUG */ PGconn *conn; /* 데이터베이스 접속 디스크럽트 구조체 */ PGresult *res; /*질의 결과를 저장할 PGresult 구조체 포인터*/ /* * 먼저, 서버 접속을 위해서 매개인자를 설정한다. * 매개인자가 널이면, 환경변수를 검사하고, * 환경변수가 설정되어 있지 않으면 시스템 기본 내정값을 사용한다. */ pghost = NULL; /* 서버의 호스트 이름 */ pgport = NULL; /* 서버 포트 */ pgoptions = NULL; /* 서버로 전달할 특별한 옵션 */ pgtty = NULL; /* 서버를 위한 디버깅 tty */ dbName = "template1"; /* 접속할 임시 데이터베이스 */ /* 데이터베이스로 접속을 시도한다. */ conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName); /* 서버와의 접속이 성공적으로 이루어졌는지 검사한다. * 만일 실패하였다면 에러 메시지를 출력하고 종료한다. */ if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, "Connection to database '\s' failed.\n", dbName); fprintf(stderr, "\s", PQerrorMessage(conn)); exit_nicely(conn); } #ifdef DEBUG /* 디버깅 파일 스트림을 열고 추적을 시작한다. */ debug = fopen("/tmp/trace.out", "w"); PQtrace(conn, debug); #endif /* DEBUG */ /* 트랜잭션 블록을 시작한다. 모든 작업은 트랜잭션 구문안에서 * 이루어져야 한다. */ res = PQexec(conn, "BEGIN"); if (PQresultStatus(res) ! = PGRES_COMMAND_OK) { fprintf(stderr, "BEGIN command failed\n"); PQclear(res); exit_nicely(conn); } /* * 메모리 유출을 막으려면 더 이상 필요하지 않는 PGresult를 * PQclear 해야 한다. */ PQclear(res); /* * 데이터베이스의 시스템 카탈로그인 pg_database 클래스에서 모든 * 데이터베이스 항목을 얻어서 커서를 선언한다. */ res = PQexec(conn, "DECLARE myportal CURSOR FOR select *from pg_database"); if (PQresultStatus(res) ! = PGRES_COMMAND_OK) { fprintf(stderr, "DECLARE CURSOR command failed\n"); PQclear(res); exit_nicely(conn); } PQclear(res); /* 선언한 커서에서 데이터를 모두 불러들인다. */ res = PQexec(conn, "FETCH ALL in myportal"); if (PQresultStatus(res) ! = PGRES_TUPLES_OK) { fprintf(stderr, "FETCH ALL command didn't return tuples properly\n"); PQclear(res); exit_nicely(conn); } /* 먼저 필드 헤더를 출력한다. */ nFields = PQnfields(res); for (i = 0; i<nFields;i++) { printf("\-15s", PQfname(res, i)); } printf("\n\n"); /* 다음으로 인스턴스 전체를 레코드 수와 필드 수만큼 출력한다. */ for (i = 0; i<PQntuples(res); i++) { for (j = 0; j<nFields; j++) { printf("\-15s", PQgetvalue(res, i, j)); } printf("\n"); } PQclear(res); /* 커서를 닫는다. 커서가 더 이상 필요없으면 커서를 닫아야 한다. */ res = PQexec(conn, "CLOSE myportal"); PQclear(res); /* 트랜잭션을 끝낸다. */ res = PQexec(conn, "END"); PQclear(res); /* 데이터베이스 접속을 종료하고 정리한다. */ PQfinish(conn); #ifdef DEBUG fclose(debug); /* 디버깅 추적을 중단한다. */ #endif /* DEBUG */ exit(0); } |
2) 간이 SQL 모니터링 프로그램 간단한 예제 프로그램을 살펴보았으므로, 이제 조금 더 색다른 프로그램을 작성해보자. 앞서와 별다를 바야 없지만 psql과 비슷한 간이 SQL 모니터링 프로그램을 작성해보겠다. psql도 사실은 libpq를 이용하여 작성한 응용 프로그램이라는 것을 앞서 설명한 바 있다. 여기에 나오는 spsql 프로그램은 psql의 기본적인 기능을 모방하여 작성한 것으로, 내부 구조는 먼저 localhost에 접속하여 pg_database 카탈로그에서 전체 데이터베이스 목록을 축출하여 사용자에게 보여주고, 사용자가 선택한 데이터베이스에 다시 접속하여 사용자의 입력을 받아서 서버에게 전달하고, 서버에서 결과를 돌려받을 경우 그것을 출력한다. #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include "libpq-fe.h" /* libpq 어플리케이션에 필요한 헤더파일 */ void main() { char *pghost = NULL; char *pgport = NULL; char *pgoptions = NULL; char *pgtty = NULL; char *dbname = NULL; int nTuples; int i, j; PGconn *conn; PGresult *res; FILE *debug; /* 디버깅 스트림 */ char database〔256〕; char buf〔2048〕, query〔2048〕; /* 기본값으로 데이터베이스에 접속한다. */ conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbname); if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr, "Connection to database '\s' failed.\n", dbname); fprintf(stderr, "\s", PQerrorMEssage(conn)); PQfinish(conn); exit(1); } /* 디버깅 정보를 얻기 위해 스트림으로 연결한다. */ debug = fopen("/tmp/trace.out", "w"); PQreace(conn, debug); |
|
RECENT COMMENT