Unix 상에서 Compile을 하는 방법은 보통 Make 명령을 사용해서 수행한다. <?XML:NAMESPACE PREFIX = O />
Make 명령은 Makefile이란 이름의 파일을 수행한다. 일종의 배치 파일형식이라고 생각하면 된다. Makefile말고도 makefile이나 각자가 원하는 파일명의 파일을 만들어서 수행할 수 있다. Make명령 뒤에 원하는 파일명을 특별히 기술하지 않는다면 우선 순위는 Makefile , makefile , make 파일명 순으로 처리 된다. 통상적으로 Makefile을 사용한다.
Makefile의 작성방법
Unix-C를 Compile할 경우에는 compile 명령인 cc 를 사용한다.
간단한 작성법을 보자면
(Unix-C에서는 ‘#’ 가 설명부이다.)
#####################################################
CC = cc # 매크로의 선언이다.
CFLAGS= -c
OFLAGS= -O -s
PGMS= test1
Test1: test1.c
@Echo "[$@] processing ..."
$(CC) $(CFLAGS) $(OFLAGS) $@.c # 컴파일을 수행한다.
$(CC) $(OFLAGS) -o test1 $@.o $(LIBS) # 실행파일을 생성한다.
######################################################
위의 Makefile내용은 test1.c라는 프로그램을 compile하고 실행파일까지 생성하는 방법을 기술한 것이다. Makefile은 매크로 기법을 사용하여 기술할 수 있는데 자세한 기법은 정확하게 기술된 서적이 없어서 자세히는 모르지만 알고 있는 범위에서 설명을 하겠다. 좀더 자세한 사항을 원한다면 Unix상에서 man(외부명령의 도움말을 볼 수 있는 명령)명령으로 make에 관한 설명을 볼 수도 있다.
매크로의 사용
매크로의 사용은 변수를 선언하고 초기치를 주는 형식과 비슷하다. 위의 예제에서 대문자의 형식으로 표현된 것이 매크로 명이고 표현은
매크로 명 = 내용
으로 사용하면 된다.
매크로의 적용은 $(매크로 명)으로 사용한다.
$@의 사용은 선언명과 대치 된다. 선언명은
선언명:
으로 선언한다.
위 프로그램에 대한 자세한 설명을 하자면 CC라는 매크로 명에 Unix-C의 compiler인 cc로 선언하고cc의 옵션처리를 위해 DFALGS, OFALGS로 매크로 명을 선언한 후 옵션을 주었다. (cc의 옵션은 나중에 설명하기로 하겠다.)
프로그램 여러 개를 한꺼번에 처리하기 위해서는 PGMS라는 선언으로 사용할 수 있다. 위의 여제는 하나의 프로그램을 처리하기 위한 방법이지만 아래의 경우는 여러 개의 프로그램을 처리하는 부분이다.
OBJS = a.o b.o
pgm: $(OBJS)
cc $(OBJS) -o pgm
a.o: incl.h a.c
cc -c a.c
b.o: incl.h b.c
cc -c b.c
일단 두개의 프로그램의 object파일명을 OBJS라는 매크로로 선언한 후 프로그램은
cc컴파일러를 이용해서 만드는데 object파일들은 OBJS라는 매크로로 선언된 파일들
이고 자기 자신에게 선언된 파일들을 가지고 만든다.
각각의 object들은 선언부의 형식으로 만들어서 사용한다.
이 밖에도 사용하는 방법은 많이 있지만 어떻게 컴파일 할 것인가에 따라 조금씩 기술하는 방법이 틀리므로 각자 필요할 때 자주 만들어 보는 것이 도움이 될 것 같다.
PRO-C의 makefile은 앞에서 설명한 부분에 ORACLE의 경로, PROC의 경로와 PRO-C를 컴파일 할 경우의 몇 가지 옵션처리를 추가해 주면 된다.
######################################################
# #
# Environ Variable for KookMin HP and Oracle 7.3.x #
# #
######################################################
# ORACLE 의 경로와 옵션 처리부
ORAINCDIR= $(ORACLE_HOME)/precomp/public
ORALIBS= -L$(ORACLE_HOME)/lib -lsql -lsqlnet -lncr -lsqlnet \
$(ORACLE_HOME)/lib/libclient.a -lcommon -lgeneric \
-lsqlnet -lncr -lsqlnet $(ORACLE_HOME)/lib/libclient.a \
-lcommon -lgeneric -lepc -lnlsrtl3 -lc3v6 -lcore3 \
-lnlsrtl3 -lcore3 -lnlsrtl3 -lcl -lm –lm
# PRO-C의 옵션 처리부
# sqlcheck에는 3가지가 있는데
# syntax => 문법만을 검사한다. (기본값)
# semantics => 문법과 sql문을 검사한다. ( 반드시 userid옵션이 포함되야 한다.)
# full => 모든 검사를 시행한다. (userid옵션 포함)
# SQL문까지 검사하고 싶으면 반드시 semantics나 full 옵션을 사용해야 한다.
PROFLAGS=ireclen=132 oreclen=132 sqlcheck=semantics userid=tips5/tips5
INCS= $(ORAINCDIR)
LIBS= $(ORALIBS)
DEFINES=
CFLAGS= -c
OFLAGS= -O -s
PGMS= test2
all: test2
test2: test2.pc
@echo "[$@] processing ..."
# iname 옵션은 pro-c에서 precompile될 소스화일을 기술하기 위한 argument이다.
$(ORACLE_HOME)/bin/proc $(PROFLAGS) iname=$@.pc
# pc로 작성된 프로그램을 Unix-C가 인식할 수 있도록 먼저 C 프로그램으로 변환한다.
$(CC) $(INCS) $(DFLAGS) $(CFLAGS) $(OFLAGS) $@.c
# 만들어진 C프로그램을 가지고 실행파일을 생성한다.
$(CC) $(DFLAGS) $(OFLAGS) -o test2 $@.o $(LIBS)
# 만들어진 C프로그램을 삭제한다. –F 옵션은 강제 삭제.
/bin/rm -f $@.c
###############################################################
precompiler의 ORACLE 옵션
이 옵션을 프로그램의 코딩에 기술할 경우에는
EXEC ORACLE OPTION( 옵션 = 값)
으로 선언해서 사용할 수 있다.
. AUTO_CONNECT=YES/NO : 자동으로 CONNECT할 것인지를 선택한다. (default는 NO)
YES일 경우는 OPS$<USERNAME>을 설정하여 자동 CONNECT
되게 한다. COMMAND LINE이나 CONFIGURATION FILE안에
기술할 수 있다.
. CODE=ANSI_C/KR_C/CPP : compiler의 COMPILE방법을 결정한다. (default는 KR_C)
COMMAND LINE 안에만 기술할 수 있다.
. COMP_CHARSET=MULTY_BYTE/SINGLE_BYTE : ????? (default는 MULTY_BYTE)
COMMAND LINE 안에만 기술할 수 있다.
. CONFIG=<filename> : configuration file 을 기술한다.
. CPP_SUFFIX=<filename extension> : CODE=CPP일 경우 설정한다. 용도는 잘 모르겠고 예로
CPP_SUFFIX=cc OR CPP_SUFFIX=C등이 있다. COMMAND LINE
이나 CONFIGURATION FILE안에 기술할 수 있다.
. DEF_SQLCODE=NO/YES : default는 NO , 만약 YES일 경우는 SQLCODE를 기술 하기 위해
#define SQLCODE sqlca.sqlcode라고 기술해야 한다.
NO일 경우는 EXEC SQL INCLUDE SQLCA; 라고 기술.
. ERRORS=YES/NO : default는 YES , YES일 경우는 터미널로 즉시 ERROR를 보여주고 LIST
파일도 가진다. NO일 경우는 그냥 LIST화일만을 가진다.
. DBMS=NATIVE/V6/V7/V6_CHAR : DBMS의 엔진을 설정한다. (default는 NATIVE)
. HOLD_CURSOR=YES/NO : CURSOR를 사용하는데 CACHE를 사용하는지 말 것 인지를
결정한다. DEFAULT는 NO, 프로그램의 performance에 영향을 많이
준다. 반복적인 SQL문이 재사용되는 것을 막아 준다(YES일 경우)
재사용되는 것을 막아주기 때문에 옵티마이져는 SQL문을 재 해석할
필요가 없는 것이다. 대체적으로 이중CURSOR를 사용할 경우 COLSE
가 두번 되는 것을 막기위해 사용된다. OPEN하고 재해석하는 시간이
줄어든다.
. INAME=<PATH 와 파일명> : INPUT FILE의 이름을 기술한다.
. ONAME=,path와 파일명 : OUTPUT FILE의 이름을 기술한다.
. INCLUDE= pathname : INCLUDE의 경로를 설정한다.
. MAXOPENCURSORS=5 ~ 255 : 최대로 열 수 있는 cursor의 수를 말한다. (default는 10개)
. MODE=ANSI/ISO/ORACLE : 문법을 맞게 검사하는 등 MODE를 설정한다. DEFAULT는
ORACLE이다.
. ORACA= YES/NO : ORACLE communication area를 설정한다. DEFAULT는 NO
. RELEASE_CURSOR= YES/NO : CURSOR를 연계해서 사용할 경우에 MEMORY CACHE의
사용을 설정한다. YES일 경우 문의 실행시마다 재해석한다. 일반적으로
실행횟수가 적은 문에서는 YES를 사용하고 빈번하게 실행되는 문에선
NO로 설정한다.
. SELECT_ERROR= YES/NO : default는 YES이다. NO일 경우에는 SELECT INTO절에서
INTO절에 받는 변수의 크기보다 더 큰 데이터가 들어왔을 때 에러를
발생하지 않는다.
. SQLCHECK=SEMANTICS/FULL/SYNTAX/LIMITED/NONE : compile시 compile 옵션체크
방법을 설정한다. DEFAULT는 SYNTAX
SEMANTICS는 반드시 USEID와 함께 사용한다. 이 옵션은 C의 문법과
SQL문의 구분까지 체크한다.
FULL 모든 검색을 실시한다. USERID를 기술한다.
SYNTAX 문법만을 검색한다.
. USERID= username/password : 오라클의 SQL문을 검색하기 위해선 오라클과 접속을 해야
한다. 오라클의 사용자 ID와 암호를 기술한다.
/* 모듈별 설명 */
우리가 작성한 모듈은 상당히 간단한 편에 속한다. 가장 까다로운 것이 99형식의 모듈이므로 00형식과 99형식에 대해서만 기술하고 다음에 나오는 예제를 보면 PRO-C에 대한 이해는 SQL문을 알고 있는 분에게는 별로 어려움이 없으리라고 본다.
/***************************************************************************
is030100.pc => 모든 필드의 값들을 tips5쪽에서 structure에 받아 이것을 가지고
데이터를 insert 하는 구문이다.
***************************************************************************/
#include <stdio.h>
/* 구조체의 정보가 들어 있는 H 파일 */
#include "Extract.h"
/* sql구문을 사용하기 위해선 반드시 선언한다. */
EXEC SQL INCLUDE sqlca;
char pg_id[9];
/*------- trace file 관련 define ==> 이행시 막을것
- tropen.h : trace file open header
- tr9999.h : character, integer type display header
- tr9998.h : doubel type display header ----------*/
#define TRACEPATH "/home/tips5/kmc210/trc/"
FILE *trace_file ;
#include "tr_extern.h"
/*------------------------------------------------------------*/
struct stk_03010000 s0301;
int is030100( stkmsg, len, errmsg )
char *stkmsg;
int len;
char *errmsg;
{
strcpy(pg_id,"is030100");
Ex01_03010000( stkmsg, s0301, len );
/*----- trace file에 변수값 display => 이행시 막을것 -------*/
/* trace file open */
tropen();
tr9999("*------------------\n");
tr9999("s0301.gogb_idno = (%s)\n", s0301.gogb_idno );
tr9999("s0301.gogb_gubun = (%s)\n", s0301.gogb_gubun );
tr9999("s0301.gogb_nameko = (%s)\n", s0301.gogb_nameko );
tr9999("s0301.gogb_pstnhm = (%s)\n", s0301.gogb_pstnhm );
tr9999("s0301.gogb_addrhm = (%s)\n", s0301.gogb_addrhm );
tr9999("s0301.gogb_telnhm = (%s)\n", s0301.gogb_telnhm );
tr9999("s0301.gogb_pstnwk = (%s)\n", s0301.gogb_pstnwk );
tr9999("s0301.gogb_addrwk = (%s)\n", s0301.gogb_addrwk );
tr9999("s0301.gogb_telnwk = (%s)\n", s0301.gogb_telnwk );
tr9999("s0301.gogb_namewk = (%s)\n", s0301.gogb_namewk );
tr9999("s0301.gogb_jobcod = (%s)\n", s0301.gogb_jobcod );
tr9999("s0301.gogb_positn = (%s)\n", s0301.gogb_positn );
tr9999("s0301.gogb_faxnwk = (%s)\n", s0301.gogb_faxnwk );
tr9999("s0301.gogb_family = (%s)\n", s0301.gogb_family );
tr9999("s0301.gogb_brthdt = (%s)\n", s0301.gogb_brthdt );
tr9999("s0301.gogb_brthcd = (%s)\n", s0301.gogb_brthcd );
tr9999("s0301.gogb_marrdt = (%s)\n", s0301.gogb_marrdt );
tr9999("s0301.gogb_movedt = (%s)\n", s0301.gogb_movedt );
tr9999("s0301.gogb_resist = (%s)\n", s0301.gogb_resist );
tr9999("s0301.gogb_sryear = (%d)\n", s0301.gogb_sryear );
tr9999("s0301.gogb_hobbcd = (%s)\n", s0301.gogb_hobbcd );
tr9999("s0301.gogb_busnum = (%s)\n", s0301.gogb_busnum );
tr9999("s0301.gogb_cornum = (%s)\n", s0301.gogb_cornum );
tr9999("s0301.gogb_prstid = (%s)\n", s0301.gogb_prstid );
tr9999("s0301.gogb_iyamt = (%d)\n", s0301.gogb_iyamt );
tr9999("s0301.gogb_iysale = (%d)\n", s0301.gogb_iysale );
tr9999("s0301.gogb_prmbnk = (%s)\n", s0301.gogb_prmbnk );
tr9999("s0301.gogb_prmbus = (%s)\n", s0301.gogb_prmbus );
tr9999("s0301.gogb_prmusr = (%s)\n", s0301.gogb_prmusr );
tr9999("s0301.gogb_prmdat = (%s)\n", s0301.gogb_prmdat );
tr9999("s0301.gogb_prmbrc = (%s)\n", s0301.gogb_prmbrc );
tr9999("s0301.gogb_idnobf = (%s)\n", s0301.gogb_idnobf );
tr9999("s0301.gogb_idnoaf = (%s)\n", s0301.gogb_idnoaf );
tr9999("s0301.gogb_inflag = (%s)\n", s0301.gogb_inflag );
tr9999("s0301.gogb_lstdat = (%s)\n", s0301.gogb_lstdat );
tr9999("s0301.gogb_rtpscn = (%d)\n", s0301.gogb_rtpscn );
tr9999("s0301.gogb_rtpsdt = (%s)\n", s0301.gogb_rtpsdt );
/*------------------------------------------------------------*/
EXEC SQL INSERT INTO 고객기본정보
VALUES (:s0301) ;
if( sqlca.sqlcode != 0 ) {
/* 중복된 데이터가 있는 경우 sqlca.sqlcode는 –1 이 된다 */
if( sqlca.sqlcode == -1 ) {
EXEC SQL DELETE FROM 고객기본정보
WHERE 주민사업자번호 = :s0301.gogb_idno ;
if( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn01 고객기본정보 table delete error!", sqlca.sqlcode, pg_id);
return( -9 );
}
else {
EXEC SQL INSERT INTO 고객기본정보
VALUES ( :s0301 ) ;
if ( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn02 고객기본정보 table delete->insert error!",
sqlca.sqlcode, pg_id );
return( -9 );
}
else
tr9999("SUCCESS \n");
return ( 0 ) ;
}
}
else {
sprintf( errmsg, "%5d %s_rtn03 고객기본정보 table insert error!", sqlca.sqlcode, pg_id );
return( -9 );
}
}
tr9999("SUCCESS \n");
return ( 0 ) ;
}
/***************************************************************************
is030199.pc => 세가지 종류의 처리가 존재한다. 기존의 정보를 새로운 정보로
변경하는 것은 같지만 그것의 처리가 복수개의 처리인지 또는
기준키의 변경인지 아닌지에 따라 로직은 변경이 될 수 있다.
Type 만을 보기 위해 중복되는 부분은 삭제함.
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "Extract.h"
EXEC SQL INCLUDE sqlca;
char pg_id[9];
struct stk_03010099 s030199;/*고객기본정보의 변경전, 변경후의 주민사업자번호*/
struct stk_03020000 s0302; /*고객기타정보 */
struct stk_03030000 s0303; /*고객정보변경내역 */
struct stk_03040000 *s0304; /*보증인정보내역 */
int is030199( stkmsg, len, errmsg )
char *stkmsg;
int len;
char *errmsg;
{
strcpy(pg_id,"is030199");
Ex01_03010099( stkmsg, s030199, len );
/**********************************/
고객기타정보를 UPDATE하는 부분
type 1..
/**********************************/
/* 변경전의 데이터를 모두 structure로 받아 둔다. */
EXEC SQL SELECT *
INTO :s0302
FROM 고객기타정보
WHERE 주민사업자번호 = :s030199.gogb_idnobf ;
if( sqlca.sqlcode == 0 ){
strcpy(s0302.gogt_idno, s030199.gogb_idnoaf) ; /*변경된 주민사업자번호를 정보에 저장 */
/* 기존의 데이터를 삭제 */
EXEC SQL DELETE FROM 고객기타정보
WHERE 주민사업자번호 = :s030199.gogb_idnobf ;
if( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn01 고객기타정보 table delete error!", sqlca.sqlcode, pg_id);
return( -9 );
}
else {
/* 변경된 주민사업자번호와 기존의 저장된데이터를 다시 insert 한다. */
EXEC SQL INSERT INTO 고객기타정보
VALUES ( :s0302 ) ;
if ( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn02 고객기타정보 table delete->insert error!", sqlca.sqlcode,
pg_id );
return( -9 );
}
}
}
else if( sqlca.sqlcode != 1403 ) {
sprintf( errmsg, "%5d %s_rtn01 고객기타정보 table select error!", sqlca.sqlcode, pg_id);
return( -9 );
}
/***************************************************************************/
회원기본정보를 UPDATE하는 부분
(복수건이 가능하지만 primary key가 아니므로 UPDATE문 처리)
type 2..
/***************************************************************************/
EXEC SQL UPDATE 회원기본정보
SET 주민사업자번호 = :s030199.gogb_idnoaf
WHERE 주민사업자번호 = :s030199.gogb_idnobf ;
if ( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn01 회원기본정보 table UPDATE error!", sqlca.sqlcode, pg_id);
return( -9 );
}
/***************************************************************************/
보증인정보등록내역을 UPDATE하는 부분
(복수건이 가능하기 때문에 cursor로 처리)
type 3..
/***************************************************************************/
/* struct array의 memory 할당과 초기화 */
if ((s0304 =
(struct stk_03040000 *) malloc(sizeof(struct stk_03040000))) == 0)
{
printf(errmsg, "s0304 Memory allocation error.\n");
return( -9 );
}
/* 복수건의 처리일 수도 있으므로 CURSOR를 이용한다 */
EXEC SQL DECLARE cur_s0304 CURSOR FOR
SELECT *
FROM 보증인정보등록내역
WHERE 주민사업자번호 = :s030199.gogb_idnobf;
EXEC SQL OPEN cur_s0304;
if(sqlca.sqlcode != 0 ){
sprintf( errmsg, "%5d %s_rtn01 보증인정보등록내역에 심각한 에러가 발생했습니다.",
sqlca.sqlcode, pg_id);
return( -9 );
}
EXEC SQL WHENEVER NOT FOUND DO break ;
for (;;)
{
EXEC SQL FETCH cur_s0304
INTO :s0304 ;
strcpy(s0304->bjin_wridno, s030199.gogb_idnoaf) ; /*변경된 주민사업자번호를 정보에 저장 */
EXEC SQL DELETE FROM 보증인정보등록내역
WHERE 주민사업자번호 = :s030199.gogb_idnobf
AND 카드계좌번호 = :s0304->bjin_wrac ;
if( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn01 보증인정보등록내역 table delete error!", sqlca.sqlcode,
pg_id);
EXEC SQL CLOSE cur_s0304;
return( -9 );
}
else {
EXEC SQL INSERT INTO 보증인정보등록내역
VALUES ( :s0304 ) ;
if ( sqlca.sqlcode != 0 ) {
sprintf( errmsg, "%5d %s_rtn02 보증인정보등록내역 table delete->insert error!",
sqlca.sqlcode, pg_id );
EXEC SQL CLOSE cur_s0304;
return( -9 );
}
}
}
EXEC SQL CLOSE cur_s0304;
tr9999("SUCCESS \n");
return( 0 );
} /*main loop */
/****************************************************************************************
예제를 이용하여 보다 쉬게 PRO-C에 접근하여 보자. 아래의 예제는 가장 기본적인 사항에 대하여 간단한 형식으로 처리한 것인데 사원번호로 사원의 이름과 봉급, 보너스를 받아서 화면에 출력하는 프로그램이다.
****************************************************************************************/
/* 기본적인 헤더화일을 연다. (특정한 헤더화일을 제외하고는 우리가 보통 사용하던 헤더화일을 생각하면 된다.) */
#include <stdio.h>
#include <string.h>
/* 사용자이름과 암호를 표현하기 위하여 자릿수를 define하고 있다. */
#define UNAME_LEN 20
#define PWD_LEN 40
/* 사용자이름과 암호가 들어갈 배열을 선언한다. */
VARCHAR username[UNAME_LEN];
VARCHAR password[PWD_LEN];
/* select 문 발행 시에 저장하게 될 변수를 structure 형식으로 선언한다. */
struct
{
VARCHAR emp_name[UNAME_LEN];
float salary;
float commission;
} emprec;
/* PRO-C에서는 표지변수(indicator)라는 것이 있다. 이것은 반드시 short형식으로 선언하며 Null값을 체크하기 위해서 많이 사용한다. 여기서도 표지변수를 사용하기 위해 선언하고 있다. */
struct
{
short emp_name_ind;
short sal_ind;
short comm_ind;
} emprec_ind;
/* 사용자에게 입력받게 될 host 변수의 선언 */
int emp_number;
int total_queried;
/* SQLCA(SQL Communications Area)를 선언한다. SQL문을 기술하기 위해서 반드시 사용하여야 한다. */
EXEC SQL INCLUDE SQLCA;
/* 에러가 발생 했을 때 발생하는 함수 선언 */
void sql_error();
main()
{
char temp_char[32];
/* VARCHAR 형식은 CHAR형식과는 다르게 길이를 가지고 있다. 그러므로 우리가
VARCHAR 형식으로 선언 하였을 경우에 그 변수 어떤 값을 입력하기 위해서는 반드
시 값의 내용과 값의 길이를 같이 넣어 주어야 한다. 이 VARCHAR 형식은 변수
명.arr 의 값이 들어가는 부분과 변수명.len이라는 길이가 들어가는 부분으로 구분되
어 있다. */
/* tips5 라는 사용자명과 암호를 변수에 저장한다. */
strncpy((char *) username.arr, "tips5", UNAME_LEN);
username.len = strlen((char *) username.arr);
strncpy((char *) password.arr, "tips5", PWD_LEN);
password.len = strlen((char *) password.arr);
/* 에러가 발생했을 경우 sql_error을 실행하라는 구문 */
EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--\n");
/* ORACLE로 접속하기 위한 구문 */
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("\nConnected to ORACLE as user: %s\n", username.arr);
/* 몇번의 query가 발행되었는지 체크하는 변수 */
total_queried = 0;
for (;;)
{
emp_number = 0;
printf("\nEnter employee number (0 to quit): ");
/* 사원번호를 입력받는다. */
gets(temp_char);
emp_number = atoi(temp_char);
/* 사원번호가 0이 입력되면 loop 종료 */
if (emp_number == 0)
break;
/* SQLCA.SQLCODE 가 1403이면 NOT FOUND가 된다. NOT FOUND 일때의 분기
하는 구문 */
EXEC SQL WHENEVER NOT FOUND GOTO notfound;
EXEC SQL SELECT ename, sal, comm
INTO :emprec INDICATOR :emprec_ind /* INTO :emprec :emprec_ind와 같다. */
FROM EMP
WHERE EMPNO = :emp_number;
printf("\n\nEmployee\tSalary\t\tCommission\n");
printf("--------\t------\t\t----------\n");
/* printf 함수는 \0 값이 나올 때까지 출력하므로 사원명의 맨 끝배열에 \0을 넣고
출력한다. */
emprec.emp_name.arr[emprec.emp_name.len] = '\0';
printf("%-8s\t%6.2f\t\t",
emprec.emp_name.arr, emprec.salary);
/* 표지변수에 들어온 값이 –1 일 경우는 NULL값이므로 ‘NULL’을 출력한다. */
if (emprec_ind.comm_ind == -1)
printf("NULL\n");
else
printf("%6.2f\n", emprec.commission);
total_queried++;
continue;
/* NOT FOUND일 경우의 분기 */
notfound:
printf("\nNot a valid employee number - try again.\n");
} /* end for(;;) */
/* 발행된 query의 수를 출력한다. */
printf("\n\nTotal rows returned was %d.\n", total_queried);
printf("\nG'day.\n\n\n");
/* Disconnect from ORACLE. */
EXEC SQL COMMIT WORK RELEASE;
/*프로그램 종료 */
exit(0);
}
void
sql_error(msg)
char *msg;
{
char err_msg[128];
size_t buf_len, msg_len;
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s\n", msg);
buf_len = sizeof (err_msg);
/* sql 의 에러 메시지를 구하는 구문, 많이 사용된다. */
sqlglm(err_msg, &buf_len, &msg_len);
printf("%.*s\n", msg_len, err_msg);
EXEC SQL ROLLBACK RELEASE;
exit(1);
}
/****************************************************************************************
우리가 SQL문을 사용하다가 보면 cursor문을 사용할 경우가 많다. 특히 PRO-C는 batch job 형식의 처리를 하기 위해서 많이 사용하는데 이런 처리를 위해서는 cursor문을 사용해야 할 경우가 많이 있다. 이번 예제는 단순한 cursor문이지만 반드시 알아 두어야 할 사항들이고 다음 예제에서 cursor를 배열로 잡아서 데이터를 한꺼번에 처리하는 cursor의 구문을 생각해 보기로 하자.
****************************************************************************************/
#include <stdio.h>
#include <string.h>
EXEC SQL INCLUDE SQLCA ;
#define UNAME_LEN 20
#define PWD_LEN 40
#define USERNAME1 "scott/tiger@PMS"
/* asciiz라는 변수의 type을 선언한다. */
typedef char asciiz[PWD_LEN];
/* 짝 */
EXEC SQL TYPE asciiz IS STRING(40) REFERENCE;
asciiz username;
asciiz password;
struct emp_info
{
asciiz emp_name;
float salary;
float commission;
};
/* errors 함수. */
void sql_error();
main()
{
/* struct 를 포인터로 재선언한다. */
struct emp_info *emp_rec_ptr;
/* 위에서 emp_rec_ptr은 아직 메모리에 할당되어 있지 않은 상태이다. 메모리를
할당한다. */
if ((emp_rec_ptr =
(struct emp_info *) malloc(sizeof(struct emp_info))) == 0)
{
fprintf(stderr, "Memory allocation error.\n");
exit(1);
}
/* Connect to ORACLE. */
strcpy(username, "SCOTT");
strcpy(password, "TIGER");
EXEC SQL WHENEVER SQLERROR DO sql_error("ORACLE error--");
/* connect */
EXEC SQL CONNECT :USERNAME1;
printf("\nConnected to ORACLE as user: %s\n", username);
/* EMP TABLE에서 SALES로 시작하는 데이터를 cursor로 잡는다. */
EXEC SQL DECLARE salespeople CURSOR FOR
SELECT ENAME, SAL, COMM
FROM EMP
WHERE JOB LIKE 'SALES%';
/* Open the cursor. */
EXEC SQL OPEN salespeople;
/* Get ready to print results. */
printf("\n\nThe company's salespeople are--\n\n");
printf("Salesperson Salary Commission\n");
printf("----------- ------ ----------\n");
/* NOT FOUND 이면 LOOP를 종료한다. */
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
/* FETCH한 데이터를 화면에 출력한다. */
EXEC SQL FETCH salespeople INTO :emp_rec_ptr;
printf("%-11s%9.2f%13.2f\n", emp_rec_ptr->emp_name,
emp_rec_ptr->salary, emp_rec_ptr->commission);
}
/* Close the cursor. */
EXEC SQL CLOSE salespeople;
printf("\nArrivederci.\n\n");
EXEC SQL COMMIT WORK RELEASE;
exit(0);
}
void
sql_error(msg)
char *msg;
{
char err_msg[512];
size_t buf_len, msg_len;
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s\n", msg);
buf_len = sizeof (err_msg);
/* sql 에러를 출력한다. */
sqlglm(err_msg, &buf_len, &msg_len);
printf("%.*s\n", msg_len, err_msg);
EXEC SQL ROLLBACK RELEASE;
exit(1);
}
/***************************************************************************
배열로 처리하는 cursor문의 예제이다.
***************************************************************************/
#include <stdio.h>
#include <string.h>
#include <sqlca.h>
#define NAME_LENGTH 20
#define ARRAY_LENGTH 5
/* connectgkrl 위한 사용자명과 암호를 변수에 저장한다. */
char *username = "SCOTT/TIGER@PMS";
char *password = "TIGER";
/* host structure의 내용을 배열로 선언한다. 크기는 ARRAY_LENGTH 만큼 */
struct
{
int emp_number[ARRAY_LENGTH];
char emp_name[ARRAY_LENGTH][NAME_LENGTH];
float salary[ARRAY_LENGTH];
} emp_rec;
/* 함수의 선언*/
void print_rows(); /* 데이터의 출력을 위한 함수 */
void sql_error();
main()
{
/* 처리된 데이터의 수를 출력하기 위하여 선언 */
int num_ret;
/* Connect to ORACLE. */
EXEC SQL WHENEVER SQLERROR DO sql_error("Connect error:");
EXEC SQL CONNECT :username;
printf("\nConnected to ORACLE as user: %s\n", username);
/* sql 문이 에러일 경우 */
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error:");
/* Declare a cursor for the FETCH. */
EXEC SQL DECLARE c1 CURSOR FOR
SELECT empno, ename, sal FROM emp;
EXEC SQL OPEN c1;
/* 처리 데이터의 개수를 위해 선언한 변수의 초기화 */
num_ret = 0;
/* NOT FOUND 이면 LOOP의 종료 */
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
EXEC SQL FETCH c1 INTO :emp_rec;
/* sqlca.sqlerrd는 여러 개의 배열을 가진다. 이 중에 sqlca.sqlerrd[2]는 자주 사용된다.
여기에는 FETCH된 데이터의 개수가 들어간다. 물론 UPDATE 문이나 INSERT
문이 발행된 후에도 처리된 개수의 데이터가 저장된다. 여기서는 structure의
배열이 5개이므로 5개를 처리하고 다시 루프를 도는 형식으로 이루어져 있다.
5개마다 데이터들은 화면에 출력될 것이다. Sqlca.sqlerrd[2]에는 계속해서 처리된
데이터가 더해져서 들어가게 된다. 아래 로직을 잘 이해해야 한다. 많이 사용되
므로 */
print_rows(sqlca.sqlerrd[2] - num_ret);
num_ret = sqlca.sqlerrd[2]; /* Reset the number. */
}
/* 마지막 루프까지 돌고나서 빠져 나왔을 때 남아 있는 데이터를 처리하기 위한
로직이다. */
if ((sqlca.sqlerrd[2] - num_ret) > 0)
print_rows(sqlca.sqlerrd[2] - num_ret);
EXEC SQL CLOSE c1;
printf("\nAu revoir.\n\n\n");
/* Disconnect from the database. */
EXEC SQL COMMIT WORK RELEASE;
exit(0);
}
void
print_rows(n)
int n;
{
int i;
printf("\nNumber Employee Salary");
printf("\n------ -------- ------\n");
for (i = 0; i < n; i++)
printf("%-9d%-15.15s%9.2f\n", emp_rec.emp_number[i],
emp_rec.emp_name[i], emp_rec.salary[i]);
}
void
sql_error(msg)
char *msg;
{
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("\n%s", msg);
printf("\n% .70s \n", sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
}
/**************************************************************************************
Dynamic SQL Method 1에 대한 기술 방법을 정리한다. 여기서는 테이블을 생성하고
데이터를 저장하고 다시 테이블을 삭제하는 방법으로 Dynamic SQL Method 1의 방법을
정리한다.
***************************************************************************************/
#include <stdio.h>
#include <string.h>
EXEC SQL INCLUDE SQLCA ;
/* ORACLE Communications Are를 포함한다. 여기에는 ORACLE 자체의 에러나 실행상태, 구문등을 */
EXEC SQL INCLUDE ORACA ;
/* ORACA.를 사용하기 위해서는 반드시 기술해야 한다. */
EXEC ORACLE OPTION (ORACA=YES);
/* 아래 프로그램에서 한번 open한 cursor를 가지고 두번 사용한 것을 볼 수 있다.
이렇게 연계되는 cursor를 사용할 경우에 사용하는 옵션이다. */
EXEC ORACLE OPTION(RELEASE_CURSOR=YES);
void sql_error();
/*type 선언*/
typedef char asciz[80];
EXEC SQL TYPE asciz IS STRING(80) REFERENCE;
main()
{
/* Declare the program host variables. */
char *username = "SCOTT";
char *password = "TIGER";
asciz dynstmt1;
char dynstmt2[10];
VARCHAR dynstmt3[80];
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error:");
/* ORACLE 에러인 경우 oraca.orastxtf에 oracle에러 메시지를 저장한다. */
oraca.orastxtf = ORASTFERR;
/* Connect to Oracle. */
EXEC SQL CONNECT :username IDENTIFIED BY :password;
puts("\nConnected to ORACLE.\n");
puts("CREATE TABLE dyn1 (col1 VARCHAR2(4))");
/* 테이블을 생성한다. Dynamic SQL Method 1의 구문은 변수를 필요로 하지 않는
SQL문일 경우에 해당된다. */
EXEC SQL EXECUTE IMMEDIATE
"CREATE TABLE dyn1 (col1 VARCHAR2(4))";
/* dynstmt1의 포인터 변수에 insert 문장을 넣고 그 변수를 Dynamic SQL 구문으로 실행
할 수는 있다. 단 SQL문안에 변수처리는 하지 못한다. */
strcpy((char *)dynstmt1, "INSERT INTO DYN1 values ('TEST')");
puts(dynstmt1);
/* insert 문장 실행 */
EXEC SQL EXECUTE IMMEDIATE :dynstmt1;
/* commit문도 변수를 사용하지는 않으므로 이 방법을 사용할 수 있다. */
strncpy(dynstmt2, "COMMIT ", 10);
printf("%.10s\n", dynstmt2);
EXEC SQL EXECUTE IMMEDIATE :dynstmt2;
/* drop형식도 이 형식으로 표현한다.. */
strcpy((char *)dynstmt3.arr, "DROP TABLE DYN1");
dynstmt3.len = strlen((char *)dynstmt3.arr);
puts((char *) dynstmt3.arr);
EXEC SQL EXECUTE IMMEDIATE :dynstmt3;
/* Commit any outstanding changes and disconnect from Oracle. */
EXEC SQL COMMIT RELEASE;
puts("\nHave a good day!\n");
return 0;
}
void
sql_error(msg)
char *msg;
{
printf("\n%.*s\n",
sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
printf("in \"%.*s...\"\n",
oraca.orastxt.orastxtl, oraca.orastxt.orastxtc);
printf("on line %d of %.*s.\n\n",
oraca.oraslnr, oraca.orasfnm.orasfnml,
oraca.orasfnm.orasfnmc);
/* 에러발생시에도 계속 */
EXEC SQL WHENEVER SQLERROR CONTINUE;
/* rollback */
EXEC SQL ROLLBACK RELEASE;
Exit(1);
}
/****************************************************************************************
sample7.pc: Dynamic SQL Method 2
dynamic SQL Method 2 는 입력변수가 1개일 경우 사용하는 방법이다.
****************************************************************************************/
#include <stdio.h>
#include <string.h>
#define USERNAME "SCOTT"
#define PASSWORD "TIGER"
#define ORASTFERR 2
EXEC SQL INCLUDE SQLCA ;
EXEC SQL INCLUDE ORACA;
EXEC ORACLE OPTION (ORACA=YES);
char *username = USERNAME;
char *password = PASSWORD;
VARCHAR dynstmt[80];
int empno = 1234;
int deptno1 = 10;
int deptno2 = 20;
void sql_error();
main()
{
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error");
/* 에러가 발생하면 현재의 SQL statement 를 저장하라는 flag */
oraca.orastxtf = ORASTFERR;
/* Connect to Oracle. */
EXEC SQL CONNECT :username IDENTIFIED BY :password;
puts("\nConnected to Oracle.\n");
/* insert 문인데 v1, v2라는 변수를 가지고 있다. 변수선언은 하지 않는다. */
strcpy(dynstmt.arr,
"INSERT INTO EMP (EMPNO, DEPTNO) VALUES (:v1, :v2)");
dynstmt.len = strlen(dynstmt.arr);
/* 변수의 값을 출력한다. */
puts((char *) dynstmt.arr);
printf(" v1 = %d, v2 = %d\n", empno, deptno1);
/* dynamic SQL method 2 구문 */
/* PREPARE로 먼저 실행될 sql문을 기술한다. */
EXEC SQL PREPARE S FROM :dynstmt;
/* 기술된 sql문에서 사용된 변수를 아래의 변수로 대체하여 실행하겠다는 구문 */
EXEC SQL EXECUTE S USING :empno, :deptno1;
/* 입력될 값의 변경 */
empno++;
printf(" v1 = %d, v2 = %d\n", empno, deptno2);
/* 이미 선언하였으므로 다시 SQL문을 선언하지는 않는다 */
EXEC SQL EXECUTE S USING :empno, :deptno2;
/* 새로운 SQL 문의 선언 */
strcpy(dynstmt.arr,
"DELETE FROM EMP WHERE DEPTNO = :v1 OR DEPTNO = :v2");
dynstmt.len = strlen(dynstmt.arr);
puts((char *) dynstmt.arr);
printf(" v1 = %d, v2 = %d\n", deptno1, deptno2);
EXEC SQL PREPARE S FROM :dynstmt;
EXEC SQL EXECUTE S USING :deptno1, :deptno2;
EXEC SQL COMMIT RELEASE;
puts("\nHave a good day!\n");
exit(0);
}
void
sql_error(msg)
char *msg;
{
printf("\n%s", msg);
printf("\n%.*s\n",
sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc);
printf("in \"%.*s...\"\n",
oraca.orastxt.orastxtl, oraca.orastxt.orastxtc);
printf("on line %d of %.*s.\n\n",
oraca.oraslnr, oraca.orasfnm.orasfnml,
oraca.orasfnm.orasfnmc);
/ * Disable ORACLE error checking to avoid an infinite loop
* should another error occur within this routine.
*/
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK RELEASE;
exit(1);
}
/ ****************************************************************************************
sample8.pc: Dynamic SQL Method 3에 관한 예제이다.
*************************************************************************************** */
#include <stdio.h>
#include <string.h>
#define USERNAME "SCOTT"
#define PASSWORD "TIGER"
#define ORASTFERR 2
EXEC SQL INCLUDE SQLCA ;
EXEC SQL INCLUDE ORACA ;
EXEC ORACLE OPTION (ORACA=YES);
char *username = USERNAME;
char *password = PASSWORD;
VARCHAR dynstmt[80];
VARCHAR ename[10];
int deptno = 10;
void sql_error();
main()
{
/* SQL statement가 에러를 발생했을 경우 subroutin을 수행 */
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error");
/* 에러 발생시 ORACA 영역에 있는 SQL Statement를 text 로 저장하라는 flag */
oraca.orastxtf = ORASTFERR;
/* Connect to Oracle. */
EXEC SQL CONNECT :username IDENTIFIED BY :password;
puts("\nConnected to Oracle.\n");
/* SQL문을 담는 변수를 VARCHAR로 선언하였으므로 값과 길이까지 넣어야 한다. v1
은 선언할 필요는 없다. 나중에 OPEN 에서 실제의 구문으로 대체된다. */
strcpy(dynstmt.arr,
"SELECT ename FROM emp WHERE deptno = :v1");
dynstmt.len = strlen(dynstmt.arr);
puts((char *) dynstmt.arr);
printf(" v1 = %d\n", deptno);
printf("\nEmployee\n");
printf("--------\n");
/* S라는 이름으로 dynstmt의 내용을 가지고 dynamic구문을 사용할 것임을 선언하고
parsing 한다 */
EXEC SQL PREPARE S FROM :dynstmt;
/*CURSOR로 S의 구문을 선언한다. */
EXEC SQL DECLARE C CURSOR FOR S;
/*CURSOR로 선언한 C의 대칭변수에 deptno라는 변수로 대입하고 CURSOR를 연다. */
EXEC SQL OPEN C USING :deptno;
/* CURSOR의 내용이 없으면 (FETCH된것이 없으면) LOOP를 빠져 나온다. */
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
/*OPEN된 CURSOR에서 데이터를 FETCH하는데 ename이라는 변수에 넣는다. */
EXEC SQL FETCH C INTO :ename;
/*들어온 변수의 배열 끝에 다른 데이터와 구분을 하기 위해서 null을 집어 넣는다.*/
ename.arr[ename.len] = '\0';
puts((char *) ename.arr);
}
/*FETCH된 ROW의 수와 SQL문을 출력한다. */
printf("\nQuery returned %d row%s.\n\n", sqlca.sqlerrd[2], (sqlca.sqlerrd[2] == 1) ? "" : "s");
/* CLOSE cursor. */
EXEC SQL CLOSE C;
EXEC SQL COMMIT RELEASE;
puts("Sayonara.\n");
exit(0);
}
void
sql_error(msg)
char *msg;
{
printf("\n%s", msg);
/* printf를 출력하기 위해 마지막 배열에 null값을 넣는다. */
sqlca.sqlerrm.sqlerrmc[sqlca.sqlerrm.sqlerrml] = '\0';
oraca.orastxt.orastxtc[oraca.orastxt.orastxtl] = '\0';
oraca.orasfnm.orasfnmc[oraca.orasfnm.orasfnml] = '\0';
printf("\n%s\n", sqlca.sqlerrm.sqlerrmc);
printf("in \"%s...\"\n", oraca.orastxt.orastxtc);
printf("on line %d of %s.\n\n", oraca.oraslnr,
oraca.orasfnm.orasfnmc);
/* 에러를 출력한 후 다시 loop를 돈다. */
EXEC SQL WHENEVER SQLERROR CONTINUE;
/* ERROR가 발생해서 CURSOR가 돌다가 빠져나오면 CURSOR의 CLOSE가 실행되지
않았으므로 에러처리 구문에서 반드시 CLOSE를 해야 한다. */
EXEC SQL CLOSE C;
EXEC SQL ROLLBACK RELEASE;
exit(1);
}
여기서 잠깐 예제에 관한 내용을 접어두고 sql문에서 사용하는 SQLCA, SQLDA, ORACA에 대해서 알아보자.
. SQLCA(SQL Communication Area: SQL 사용자 영역) : sql문을 기술할 경우에 우리는 이 SQL Communication Area를 무시할 수 없다. 이 영역은 여러 개의요소들을 가지고 있는데 그 중에서 우리가 가장 잘 알고 있는 부분은 SQLCODE이다. 차례차례 SQLCA가 가지고 있는 영역에 대하여 알아보자. 여기서는 PRO-C에서의 SQLCA를 중점으로 애기한다.
SQLCA는 structure 구조로 되어 있다. 우리는 보통 프로그램 코드 상에서 EXEC SQL INCLUDE SQLCA; 라고 하면 이 구조체를 사용할 수가 있다. 또한 이 구조체를 직접 프로그램의 코딩에 기술할 수도 있다. 직접 기술하면 구조체의 임의의 이름을 주어서 기술할 수도 있다. SQLCA의 구조는 다음과 같다.
Struct sqlca {
Char sqlcaid[8] ;
Long sqlcabc;
Long sqlcode;
Struct {
Unsigned short sqlerrml ;
Char sqlerrmc[70] ;
} sqlerrm ;
char sqlerrp[8] ;
long sqlerrd[6] ;
char sqlwarn[8] ;
char sqlext[8] ;
} ;
struct sqlca sqlca ;
SQLCA는 프로그램 코딩에서 whenever문과 같이 자주 사용하는데 구문은
EXEC SQL WHENEVER anyerror CONTINUE;
와 같이 사용된다. 이 구문은 에러가 발생되어도 계속해서 실행하기를 원할 경우에 사용되는 구문이다. 이 밖에 GOTO문이나 DO문을 사용해서 분기시킬 수도 있다.
1. sqlcaid와 sqlcabc => PL/SQL문이나 C에서는 사용하지 않는다. 이것은 FORTRAN을 위한
필드이다.
2. sqlca.sqlcode => 에러의 코드가 저장되는 필드로 크게 3가지로 구분된다. 0 과 음수 , 양수
0 일경우는 정상종료임을 뜻한다.
양수일 경우는 1403 한가지 뿐인데 데이터를 찾지 못했을 경우에 나타난다.
PL/SQL에서는 100으로 나타난다.
음수일 경우는 상당히 많은 코드를 가지고 있다. 무조건 에러이다.
-1는 중복해서 데이터가 있을 경우에 발생한다.
3. sqlerrm => 이 필드는 sqlerrml과 sqlerrmc로 나누어져 있다. sqlerrml은 sqlerrmc의 길이를
저장한다. sqlerrmc는 에러의 text구문을 저장한다. 우리가 일반적인 에러구문
을 사용자에게 보여주기 위해선 이 필드를 이용하면 된다.
PL/SQL에서는 함수처럼 사용할 수도 있다. 즉, SQLERRM(100)이라고 하면
MESSAGE는 ORA-01403: no data found가 된다.
4. sqlerrp => 지금은 사용하지 않는 필드이다.
5. Sqlerrd => 이 필드는 6개의 배열을 가진다. 그러나 3번째 배열만을 사용하고 나머지는
지금은 사용하지 않는다.
Sqlerrd[2]는 cursor의 fetch된 row의 수나 update, delete할 경우 처리된 데이터
의 개수들을 기억한다. 매우 자주 사용한다.
PL/SQL에서는 SQL%ROWCOUNT로 체크할 수 있고 파워빌더에SQLNROWS
로 체크할 수 있다.
6. sqlwarn => 8개의 배열을 가지고 있다. 프리컴파일 도중에 난 경고구문에 대해 저장을
한다. 각각의 경고를 다 알 수는 없지만 경고가 발생 되었을 경우에는 언제나
[0] 번째에 “W”의 값이 저장된다.
7. sqlext => 현재는 사용하지 않는다.
. SQLDA( SQL Descriptor Area : SQL 기술자 영역) : 우리가 dynamic SQL 4를 사용하기 위해선 SQLDA를 알아야 한다. dynamic SQL 4에서는 반드시 SQLDA를 선언해 주어야 하기 때문이다. 이 SQLDA는 DESCRIBE문에 의해 할당된 영역을 말한다. 이 SQLDA문은 두 가지의 목적으로 사용된다. 첫째는 SELECT 문에 의해 반환된 항목을 보존하기 위해 사용하는 경우와 바인드 변수( dynamic SQL의 입력변수)에 대한 정보를 보존하기 위해서 사용된다.
SQLDA의 구조는 다음과 같다.
Struct sqlda{
Int N;
Char **V;
Int *L;
Short *T;
Short *I;
Int F;
Char **S;
Short *M;
Short *C;
Char **X;
Short *Y;
Short *Z;
};
typedef struct sqlda sqlda;
. ORACA(ORACLE Communication Area) : SQLCA에서 얻지 못하는 정보를 얻기 위해서 사용된다.
EXEC SQL INCLUDE ORACA; 라고 선언하고
EXEC ORACLE OPTION( ORACA = YES ) 라고 기술해 주어야 한다.
ORACA에 포함되어 있는 항목들에 대해서 정리해보자.
1. orastxt => ORACLE RDBMS가 해석한 현재 SQL문의 text를 저장하고 있다. 에러가 발생
했을 경우 에러가 있는 SQL문을 검색하고자 할 경우에 자주 사용된다. 프리
컴파일러가 해석하는 CONNECT, COMMIT FETCH등의 문장은 저장되지 않는다.
orastxtc와 orastxtl로 구성되어 있다.
2. orasfnm => 여러 개의 파일을 컴파일하는 경우에 ORACLE은 에러가 발생한 파일을 이 필
드에 저장한다. orasfnmc와 orasfnml 으로 구성되어 있다.
3. oraslnr => 에러가 발생한 행의 번호를 넘겨준다.
4. Orastxtf => 에러가 발생했을 경우 orastxt에 에러가 발생한 구문을 저장하는데 이때 어떻
게 저장할 것인지를 결정한다.
0 : default값 SQL문을 보존하지 않는다.
1 : SQLERROR가 있는 SQL문만을 보존
2 : SQLERROR 및 SQLWARN이 있는 SQL문만을 보존
3 : SQL문 전부를 보존한다.
5. 커서 캐쉬에 대한 통계를 위한 필드들
orahoc : 지정된 최대 오픈 커서
oramoc : 필요한 최대 커서
oracoc : guswo 사용중인 커서의 수
oranor : 커서 캐쉬 재할당의 회수
oranpr : SQL문의 해석한 횟수
oranex : SQL문의 실행횟수
/*************************************************************
Sample Program 9: Calling a stored procedure
ORACLE의 SP를 CALL할 수 있는 예제이다.
*************************************************************/
#include <stdio.h>
#include <string.h>
EXEC SQL INCLUDE SQLCA;
typedef char asciz[20];
typedef char vc2_arr[11];
EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL TYPE asciz IS STRING(20) REFERENCE;
EXEC SQL TYPE vc2_arr IS VARCHAR2(11) REFERENCE;
asciz username;
asciz password;
int dept_no; /* which department to query? */
vc2_arr emp_name[10]; /* array of returned names */
vc2_arr job[10];
float salary[10];
int done_flag;
int array_size;
int num_ret; /* number of rows returned */
EXEC SQL END DECLARE SECTION;
long SQLCODE;
void print_rows(); /* produces program output */
void sql_error(); /* handles unrecoverable errors */
main()
{
int i;
char temp_buf[32];
EXEC SQL WHENEVER SQLERROR DO sql_error();
strcpy(username, "scott");
strcpy(password, "tiger");
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("\nConnected to ORACLE as user: %s\n\n", username);
printf("Enter department number: ");
/*사원번호를 입력받는다. */
gets(temp_buf);
dept_no = atoi(temp_buf);
/*출력될 양식의header를 출력한다. */
printf("\n\n");
printf("%-10.10s%-10.10s%s\n", "Employee", "Job", "Salary");
printf("%-10.10s%-10.10s%s\n", "--------", "---", "------");
/* 배열에 쓰일 변수의 초기화 */
array_size = 10;
done_flag = 0;
num_ret = 0;
for (;;)
{
EXEC SQL EXECUTE
/* calldemo라는 package안의 get_employees라는 procedure를 호출한다.
괄호안의 변수를은 procedure의 입출력변수와 대칭된다. Done_flag이후의
변수들은 procedure의 OUT변수들이다.*/
BEGIN calldemo.get_employees
(:dept_no, :array_size, :num_ret, :done_flag,
:emp_name, :job, :salary);
END;
END-EXEC;
print_rows(num_ret);
/* procedure의 done_flag값이 true이면 종료*/
if (done_flag)
break;
}
EXEC SQL COMMIT WORK RELEASE;
exit(0);
}
void
print_rows(n)
int n;
{
int i;
if (n == 0)
{
printf("No rows retrieved.\n");
return;
}
/*처리된 순서대로 procedure에서 넘어온 값들을 출력한다. */
for (i = 0; i < n; i++)
printf("%10.10s%10.10s%6.2f\n",
emp_name[i], job[i], salary[i]);
}
void
sql_error()
{
char msg[512];
size_t buf_len, msg_len;
EXEC SQL WHENEVER SQLERROR CONTINUE;
buf_len = sizeof(msg);
/* sql 의 에러 메시지를 구하는 구문 */
sqlglm(msg, &buf_len, &msg_len);
printf("\nORACLE error detected:");
printf("\n%.*s \n", msg_len, msg);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
}
/ ****************************************************************************************
사원데이블을 fetch하는 프로그램이다. 처리하는 로직은 stored procedure로 만들어져
있으며 EMP_DEMO_PKG 라는 이름의 package안에 open_cur라는 이름으로 되어 있다. 여기서
는 procedure 안에 있는 로직을 cursor형의 임의의 변수로 잡아서 그 변수를 allocate라는 명령
으로 PL/SQL block 안에서 참조할 수 있도록 할당해서 사용하고 있다.
***************************************************************************************/
#include <stdio.h>
EXEC SQL INCLUDE SQLCA ;
void sql_error();
main()
{
char temp[32];
EXEC SQL BEGIN DECLARE SECTION;
char *uid = "scott/tiger";
/* CURSOR형으로 변수를 선언한다. ALLOCATE 와 반드시 같이 사용해야 한다.*/
SQL_CURSOR emp_cursor;
int dept_num;
/* Fetch 한 값을 저장하기 위한 STRUCTURE */
struct
{
int emp_num;
char emp_name[11];
char job[10];
int manager;
char hire_date[10];
float salary;
float commission;
int dept_num;
} emp_info;
/* Fetch 한 데이터의 표지변수 선언을 위한 STRUCTURE */
struct
{
short emp_num_ind;
short emp_name_ind;
short job_ind;
short manager_ind;
short hire_date_ind;
short salary_ind;
short commission_ind;
short dept_num_ind;
} emp_info_ind;
EXEC SQL END DECLARE SECTION;
EXEC SQL WHENEVER SQLERROR do sql_error("Oracle error");
/* Connect to Oracle. */
EXEC SQL CONNECT :uid;
/* cursor 변수를 pl/sql block에 할당한다. */
EXEC SQL ALLOCATE :emp_cursor;
/* 데이터가 존재하지 않을 때 loop를 빠져 나온다. */
EXEC SQL WHENEVER NOT FOUND DO break;
for (;;)
{
printf("\nEnter department number (0 to exit): ");
/* 거래처를 입력받는다 0 이면 exit */
gets(temp);
dept_num = atoi(temp);
if (dept_num <= 0)
break;
/* procedure의 실행 */
EXEC SQL EXECUTE
begin
emp_demo_pkg.open_cur(:emp_cursor, :dept_num);
end;
END-EXEC;
printf("\nFor department %d--\n", dept_num);
printf("ENAME\t SAL\t COMM\n");
printf("-----\t ---\t ----\n");
for (;;)
{
EXEC SQL FETCH :emp_cursor
INTO :emp_info INDICATOR :emp_info_ind;
printf("%s\t", emp_info.emp_name);
printf("%8.2f\t\t", emp_info.salary);
if (emp_info_ind.commission_ind != 0)
printf(" NULL\n");
else
printf("%8.2f\n", emp_info.commission);
}
}
/* Close the cursor. */
EXEC SQL CLOSE :emp_cursor;
exit(0);
}
void
sql_error(msg)
char *msg;
{
long clen, fc;
char cbuf[128];
clen = (long) sizeof (cbuf);
sqlgls(cbuf, &clen, &fc);
printf("\n%s\n", msg);
printf("Statement is--\n%s\n", cbuf);
printf("Function code is %ld\n\n", fc);
sqlglm(cbuf, (int *) &clen, (int *) &clen);
printf ("\n%.*s\n", clen, cbuf);
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK WORK;
exit(-1);
}
/*******************************************************************
Sample Program 11: Dynamic SQL Method 4
You can enter multi-line statements. The limit is 1023 characters.
This sample program only processes up to MAX_ITEMS bind variables and
MAX_ITEMS select-list items. MAX_ITEMS is #defined to be 40.
*******************************************************************/
#include <stdio.h>
#include <string.h>
/* setjmp 함수를 쓰기 위해 선언되었다. */
#include <setjmp.h>
#define MAX_ITEMS 40
#define MAX_VNAME_LEN 30
#define MAX_INAME_LEN 30
#ifndef NULL
#define NULL 0
#endif
char *dml_commands[] = {"SELECT", "select", "INSERT", "insert",
"UPDATE", "update", "DELETE", "delete"};
EXEC SQL BEGIN DECLARE SECTION;
char dyn_statement[1024];
EXEC SQL VAR dyn_statement IS STRING(1024);
EXEC SQL END DECLARE SECTION;
EXEC SQL INCLUDE sqlca;
/* SQL Descriptor AREA선언 */
EXEC SQL INCLUDE sqlda;
/* SQLDA TYPE의 변수선언 */
SQLDA *bind_dp;
SQLDA *select_dp;
/* EXTERN 함수 선언 */
extern SQLDA *sqlald();
extern void sqlnul();
/* Define a buffer to hold longjmp state info. */
jmp_buf jmp_continue;
/* 에러를 검색하기 위한 전역변수. */
int parse_flag = 0;
main()
{
int oracle_connect();
int alloc_descriptors();
int get_dyn_statement();
int set_bind_variables();
int process_select_list();
int i;
/* Connect to the database. */
if (oracle_connect() != 0)
exit(1);
/* Allocate memory for the select and bind descriptors. */
if (alloc_descriptors(MAX_ITEMS, MAX_VNAME_LEN, MAX_INAME_LEN) != 0)
exit(1);
/* Process SQL statements. */
for (;;)
{
/* 프로그램의 현재상태를 저장하는 함수이다. */
i = setjmp(jmp_continue);
/* Get the statement. Break on "exit". */
if (get_dyn_statement() != 0)
break;
EXEC SQL WHENEVER SQLERROR DO sql_error();
parse_flag = 1; /* Set a flag for sql_error(). */
EXEC SQL PREPARE S FROM :dyn_statement;
parse_flag = 0; /* Unset the flag. */
EXEC SQL DECLARE C CURSOR FOR S;
/* Set the bind variables for any placeholders in the SQL statement. */
set_bind_variables();
EXEC SQL OPEN C USING DESCRIPTOR bind_dp;
process_select_list();
/* Tell user how many rows processed. */
for (i = 0; i < 8; i++)
{
if (strncmp(dyn_statement, dml_commands[i], 6) == 0)
{
printf("\n\n%d row%c processed.\n", sqlca.sqlerrd[2],
sqlca.sqlerrd[2] == 1 ? '\0' : 's');
break;
}
}
} /* end of for(;;) statement-processing loop */
/* When done, free the memory allocated for pointers in the bind and select descriptors. */
for (i = 0; i < MAX_ITEMS; i++)
{
if (bind_dp->V[i] != (char *) 0)
free(bind_dp->V[i]);
free(bind_dp->I[i]); /* MAX_ITEMS were allocated. */
if (select_dp->V[i] != (char *) 0)
free(select_dp->V[i]);
free(select_dp->I[i]); /* MAX_ITEMS were allocated. */
}
/* Free space used by the descriptors themselves. */
sqlclu(bind_dp);
sqlclu(select_dp);
EXEC SQL WHENEVER SQLERROR CONTINUE;
/* Close the cursor. */
EXEC SQL CLOSE C;
EXEC SQL COMMIT WORK RELEASE;
puts("\nHave a good day!\n");
EXEC SQL WHENEVER SQLERROR DO sql_error();
return;
}
/* 접속을 위한 함수 */
oracle_connect()
{
EXEC SQL BEGIN DECLARE SECTION;
VARCHAR username[128];
VARCHAR password[32];
EXEC SQL END DECLARE SECTION;
printf("\nusername: ");
fgets((char *) username.arr, sizeof username.arr, stdin);
fflush(stdin);
username.arr[strlen((char *) username.arr)-1] = '\0';
username.len = strlen((char *) username.arr);
printf("password: ");
fgets((char *) password.arr, sizeof password.arr, stdin);
fflush(stdin);
password.arr[strlen((char *) password.arr) - 1] = '\0';
password.len = strlen((char *) password.arr);
EXEC SQL WHENEVER SQLERROR GOTO connect_error;
EXEC SQL CONNECT :username IDENTIFIED BY :password;
printf("\nConnected to ORACLE as user %s.\n", username.arr);
return 0;
connect_error:
fprintf(stderr, "Cannot connect to ORACLE as user %s\n", username.arr);
return -1;
}
/*
* Allocate the BIND and SELECT descriptors using sqlald().
* Also allocate the pointers to indicator variables
* in each descriptor. The pointers to the actual bind
* variables and the select-list items are realloc'ed in
* the set_bind_variables() or process_select_list()
* routines. This routine allocates 1 byte for select_dp->V[i]
* and bind_dp->V[i], so the realloc will work correctly.
*/
alloc_descriptors(size, max_vname_len, max_iname_len)
int size;
int max_vname_len;
int max_iname_len;
{
int i;
/*
* The first sqlald parameter determines the maximum number of
* array elements in each variable in the descriptor. In
* other words, it determines the maximum number of bind
* variables or select-list items in the SQL statement.
*
* The second parameter determines the maximum length of
* strings used to hold the names of select-list items
* or placeholders. The maximum length of column
* names in ORACLE is 30, but you can allocate more or less
* as needed.
*
* The third parameter determines the maximum length of
* strings used to hold the names of any indicator
* variables. To follow ORACLE standards, the maximum
* length of these should be 30. But, you can allocate
* more or less as needed.
*/
if ((bind_dp =
sqlald(size, max_vname_len, max_iname_len)) == (SQLDA *) 0)
{
fprintf(stderr,
"Cannot allocate memory for bind descriptor.");
return -1; /* Have to exit in this case. */
}
if ((select_dp =
sqlald (size, max_vname_len, max_iname_len)) == (SQLDA *) 0)
{
fprintf(stderr,
"Cannot allocate memory for select descriptor.");
return -1;
}
select_dp->N = MAX_ITEMS;
/* Allocate the pointers to the indicator variables, and the
actual data. */
for (i = 0; i < MAX_ITEMS; i++) {
bind_dp->I[i] = (short *) malloc(sizeof (short));
select_dp->I[i] = (short *) malloc(sizeof(short));
bind_dp->V[i] = (char *) malloc(1);
select_dp->V[i] = (char *) malloc(1);
}
return 0;
}
get_dyn_statement()
{
char *cp, linebuf[256];
int iter, plsql;
int help();
for (plsql = 0, iter = 1; ;)
{
if (iter == 1)
{
printf("\nSQL> ");
dyn_statement[0] = '\0';
}
fgets(linebuf, sizeof linebuf, stdin);
fflush(stdin);
cp = strrchr(linebuf, '\n');
if (cp && cp != linebuf)
*cp = ' ';
else if (cp == linebuf)
continue;
if ((strncmp(linebuf, "EXIT", 4) == 0) ||
(strncmp(linebuf, "exit", 4) == 0))
{
return -1;
}
else if (linebuf[0] == '?' ||
(strncmp(linebuf, "HELP", 4) == 0) ||
(strncmp(linebuf, "help", 4) == 0))
{
help();
iter = 1;
continue;
}
if (strstr(linebuf, "BEGIN") ||
(strstr(linebuf, "begin")))
{
plsql = 1;
}
strcat(dyn_statement, linebuf);
if ((plsql && (cp = strrchr(dyn_statement, '/'))) ||
(!plsql && (cp = strrchr(dyn_statement, ';'))))
{
*cp = '\0';
break;
}
else
{
iter++;
printf("%3d ", iter);
}
}
return 0;
}
set_bind_variables()
{
int i, n;
char bind_var[64];
/* Describe any bind variables (input host variables) */
EXEC SQL WHENEVER SQLERROR DO sql_error();
bind_dp->N = MAX_ITEMS; /* Initialize count of array elements. */
EXEC SQL DESCRIBE BIND VARIABLES FOR S INTO bind_dp;
/* If F is negative, there were more bind variables
than originally allocated by sqlald(). */
if (bind_dp->F < 0)
{
printf ("\nToo many bind variables (%d), maximum is %d\n.",
-bind_dp->F, MAX_ITEMS);
return;
}
/* Set the maximum number of array elements in the
descriptor to the number found. */
bind_dp->N = bind_dp->F;
/* Get the value of each bind variable as a
* character string.
*
* C[i] contains the length of the bind variable
* name used in the SQL statement.
* S[i] contains the actual name of the bind variable
* used in the SQL statement.
*
* L[i] will contain the length of the data value
* entered.
*
* V[i] will contain the address of the data value
* entered.
*
* T[i] is always set to 1 because in this sample program
* data values for all bind variables are entered
* as character strings.
* ORACLE converts to the table value from CHAR.
*
* I[i] will point to the indicator value, which is
* set to -1 when the bind variable value is "null".
*/
for (i = 0; i < bind_dp->F; i++)
{
printf ("\nEnter value for bind variable %.*s: ",
(int)bind_dp->C[i], bind_dp->S[i]);
fgets(bind_var, sizeof bind_var, stdin);
/* Get length and remove the new line character. */
n = strlen(bind_var) - 1;
/* Set it in the descriptor. */
bind_dp->L[i] = n;
/* (re-)allocate the buffer for the value.
sqlald() reserves a pointer location for
V[i] but does not allocate the full space for
the pointer. */
bind_dp->V[i] = (char *) realloc(bind_dp->V[i],
(bind_dp->L[i] + 1));
/* And copy it in. */
strncpy(bind_dp->V[i], bind_var, n);
/* Set the indicator variable's value. */
if ((strncmp(bind_dp->V[i], "NULL", 4) == 0) ||
(strncmp(bind_dp->V[i], "null", 4) == 0))
*bind_dp->I[i] = -1;
else
*bind_dp->I[i] = 0;
/* Set the bind datatype to 1 for CHAR. */
bind_dp->T[i] = 1;
}
}
process_select_list()
{
int i, null_ok, precision, scale;
if ((strncmp(dyn_statement, "SELECT", 6) != 0) &&
(strncmp(dyn_statement, "select", 6) != 0))
{
select_dp->F = 0;
return;
}
/* If the SQL statement is a SELECT, describe the
select-list items. The DESCRIBE function returns
their names, datatypes, lengths (including precision
and scale), and NULL/NOT NULL statuses. */
select_dp->N = MAX_ITEMS;
EXEC SQL DESCRIBE SELECT LIST FOR S INTO select_dp;
/* If F is negative, there were more select-list
items than originally allocated by sqlald(). */
if (select_dp->F < 0)
{
printf ("\nToo many select-list items (%d), maximum is %d\n",
-(select_dp->F), MAX_ITEMS);
return;
}
/* Set the maximum number of array elements in the
descriptor to the number found. */
select_dp->N = select_dp->F;
/* Allocate storage for each select-list item.
sqlprc() is used to extract precision and scale
from the length (select_dp->L[i]).
sqlnul() is used to reset the high-order bit of
the datatype and to check whether the column
is NOT NULL.
CHAR datatypes have length, but zero precision and
scale. The length is defined at CREATE time.
NUMBER datatypes have precision and scale only if
defined at CREATE time. If the column
definition was just NUMBER, the precision
and scale are zero, and you must allocate
the required maximum length.
DATE datatypes return a length of 7 if the default
format is used. This should be increased to
9 to store the actual date character string.
If you use the TO_CHAR function, the maximum
length could be 75, but will probably be less
(you can see the effects of this in SQL*Plus).
ROWID datatype always returns a fixed length of 18 if
coerced to CHAR.
LONG and
LONG RAW datatypes return a length of 0 (zero),
so you need to set a maximum. In this example,
it is 240 characters.
*/
printf ("\n");
for (i = 0; i < select_dp->F; i++)
{
/* Turn off high-order bit of datatype (in this example,
it does not matter if the column is NOT NULL). */
sqlnul (&(select_dp->T[i]), &(select_dp->T[i]), &null_ok);
switch (select_dp->T[i])
{
case 1 : /* CHAR datatype: no change in length
needed, except possibly for TO_CHAR
conversions (not handled here). */
break;
case 2 : /* NUMBER datatype: use sqlprc() to
extract precision and scale. */
sqlprc (&(select_dp->L[i]), &precision, &scale);
/* Allow for maximum size of NUMBER. */
if (precision == 0) precision = 40;
/* Also allow for decimal point and
possible sign. */
/* convert NUMBER datatype to FLOAT if scale > 0,
INT otherwise. */
if (scale > 0)
select_dp->L[i] = sizeof(float);
else
select_dp->L[i] = sizeof(int);
break;
case 8 : /* LONG datatype */
select_dp->L[i] = 240;
break;
case 11 : /* ROWID datatype */
select_dp->L[i] = 18;
break;
case 12 : /* DATE datatype */
select_dp->L[i] = 9;
break;
case 23 : /* RAW datatype */
break;
case 24 : /* LONG RAW datatype */
select_dp->L[i] = 240;
break;
}
/* Allocate space for the select-list data values.
sqlald() reserves a pointer location for
V[i] but does not allocate the full space for
the pointer. */
if (select_dp->T[i] != 2)
select_dp->V[i] = (char *) realloc(select_dp->V[i],
select_dp->L[i] + 1);
else
select_dp->V[i] = (char *) realloc(select_dp->V[i],
select_dp->L[i]);
/* Print column headings, right-justifying number
column headings. */
if (select_dp->T[i] == 2)
if (scale > 0)
printf ("%.*s ", select_dp->L[i]+3, select_dp->S[i]);
else
printf ("%.*s ", select_dp->L[i], select_dp->S[i]);
else
printf ("%-.*s ", select_dp->L[i], select_dp->S[i]);
/* Coerce ALL datatypes except for LONG RAW and NUMBER to
character. */
if (select_dp->T[i] != 24 && select_dp->T[i] != 2)
select_dp->T[i] = 1;
/* Coerce the datatypes of NUMBERs to float or int depending on
the scale. */
if (select_dp->T[i] == 2)
if (scale > 0)
select_dp->T[i] = 4; /* float */
else
select_dp->T[i] = 3; /* int */
}
printf ("\n\n");
/* FETCH each row selected and print the column values. */
EXEC SQL WHENEVER NOT FOUND GOTO end_select_loop;
for (;;)
{
EXEC SQL FETCH C USING DESCRIPTOR select_dp;
/* Since each variable returned has been coerced to a
character string, int, or float very little processing
is required here. This routine just prints out the
values on the terminal. */
for (i = 0; i < select_dp->F; i++)
{
if (*select_dp->I[i] < 0)
if (select_dp->T[i] == 4)
printf ("%-*c ",(int)select_dp->L[i]+3, ' ');
else
printf ("%-*c ",(int)select_dp->L[i], ' ');
else
if (select_dp->T[i] == 3) /* int datatype */
printf ("%*d ", (int)select_dp->L[i],
*(int *)select_dp->V[i]);
else if (select_dp->T[i] == 4) /* float datatype */
printf ("%*.2f ", (int)select_dp->L[i],
*(float *)select_dp->V[i]);
else /* character string */
printf ("%-*.*s ", (int)select_dp->L[i],
(int)select_dp->L[i], select_dp->V[i]);
}
printf ("\n");
}
end_select_loop:
return;
}
help()
{
puts("\n\nEnter a SQL statement or a PL/SQL block at the SQL> prompt.");
puts("Statements can be continued over several lines, except");
puts("within string literals.");
puts("Terminate a SQL statement with a semicolon.");
puts("Terminate a PL/SQL block (which can contain embedded semicolons)");
puts("with a slash (/).");
puts("Typing \"exit\" (no semicolon needed) exits the program.");
puts("You typed \"?\" or \"help\" to get this message.\n\n");
}
sql_error()
{
int i;
/* ORACLE error handler */
printf ("\n\n%.70s\n",sqlca.sqlerrm.sqlerrmc);
if (parse_flag)
printf
("Parse error at character offset %d in SQL statement.\n",
sqlca.sqlerrd[4]);
EXEC SQL WHENEVER SQLERROR CONTINUE;
EXEC SQL ROLLBACK WORK;
longjmp(jmp_continue, 1);
}