9. 응용프로그램 작성#
이 장에서는 Altibase를 활용하는 응용 프로그램 작성법에 관하여 간략하게 설명하도록 한다.
응용 프로그램 작성 방법#
Altibase CLI, JDBC, ODBC, APRE C/C++ Precompiler 등을 사용하여 Altibase 응용 프로그램을 개발하는 것은 범용 데이터베이스 운용 환경에서의 응용 프로그램 작성법과 크게 다르지 않다.
본 장에서는 Altibase 응용 프로그램 작성법에 관하여 간략하게 설명하도록 한다.
응용 프로그램 작성의 자세한 내용은 CLI User's Manual, ODBC User's Manual, Precompiler User's Manual, 및 API User's Manual을 참조한다.
Altibase CLI를 활용한 프로그램#
이 절은 Altibase CLI를 이용하여 클라이언트 응용 프로그램를 작성하는 방법을 설명한다. Altibase CLI는 Altibase를 클라이언트-서버 구조로 운영하는 상황에서 사용할 수 있는 API이다. 자세한 내용은 CLI User's Manual을 참조한다.
헤더 파일과 라이브러리#
Altibase CLI를 이용하여 프로그램을 작성하기 위해서는 Altibase의 설치 홈 디렉토리의 서브 디렉토리인 include와 lib에 있는 헤더 파일과 라이브러리 파일이 필요하다.
$ALTIBASE_HOME/include/sqlcli.h
$ALTIBASE_HOME/lib/libodbccli.a
Makefile#
작성된 Altibase CLI 소스를 컴파일하기 위한 Makefile에는 다음과 같은 내용이 포함되어야 한다.
include $(ALTIBASE_HOME)/install/altibase_env.mk
이 파일 안에는 컴파일시 필요로 하는 라이브러리 경로와 라이브러리들을 링크시키기 위한 옵션들이 정의되어 있으며 오브젝트 파일을 만드는 규칙을 제공한다. $ALTIBASE_HOME/sample/SQLCLI 내의 Makefile을 참조한다.
예문#
include $(ALTIBASE_HOME)/install/altibase_env.mk
SRCS=
OBJS=$(SRCS:.cpp=.$(OBJEXT))
BINS=demo_ex1
all: $(BINS)
demo_ex1: demo_ex1.$(OBJEXT)
$(LD) $(LFLAGS) $(LDOUT)demo_ex1$(BINEXT) demo_ex1.$(OBJEXT) $(LIBOPT)odbccli$(LIBAFT) $(LIBS)
멀티 쓰레드 프로그램#
멀티 쓰레드 프로그램을 작성할 때 다음과 같은 사항을 주의해야 한다.
- 각 쓰레드 별로 환경 핸들, 연결 핸들을 각각 할당해야 한다.
프로그램 작성#
Altibase CLI를 이용하여 작성하는 프로그램에서 Altibase 서버에 접속하고 종료하는 방법을 간략하게 설명하도록 한다.
Altibase CLI 프로그램의 예#
/* test.cpp */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <sqlcli.h>
void sbigint_bigint(int cnt);
void slong_integer(int cnt);
void char_char(int cnt);
void char_number(int cnt);
void double_double(int cnt);
void prepare();
void execute();
void usage();
long logMsec(const char *astr);
void conn(char *port, char *conntype);
#define MSG_LEN 1024
SQLHENV env; // Environment를 할당 받을 handle
SQLHDBC con; // Connection을 할당 받을 handle
SQLHSTMT hstmt; // Statement를 할당 받을 handle
SQLHSTMT bstmt; // Statement를 할당 받을 handle
int errNo;
short msgLength;
char errMsg[MSG_LEN];
SQLRETURN rc;
/* Main프로그램 - 인자 개수가 5보다 적을 경우 사용법(usage) 출력 */
int main(int ac, char **av)
{
if (ac < 5)
{
usage();
}
conn(av[2], av[3]);
switch(atoi(av[1]))
{
case 1:
logMsec(" BIGINT - START TIME : ");
sbigint_bigint(atoi(av[4]));
logMsec(" BIGINT - END TIME : ");
break;
case 2:
logMsec(" INTEGER - START TIME : ");
slong_integer(atoi(av[4]));
logMsec(" INTEGER - END TIME : ");
break;
case 3:
logMsec(" CHAR - START TIME : ");
char_char(atoi(av[4]));
logMsec(" CHAR - END TIME : ");
break;
case 4:
logMsec(" NUMBER - START TIME : ");
char_number(atoi(av[4]));
logMsec(" NUMBER - END TIME : ");
break;
case 5:
logMsec(" DOUBLE - START TIME : ");
double_double(atoi(av[4]));
logMsec(" DOUBLE - END TIME : ");
break;
}
}
/* 프로그램의 사용법을 출력 */
void usage()
{
printf("Usage: ./test <program_no> <port_no> <conntype> <cnt>\n");
printf("\tprogram_no : 1 => \t SBIGINT-BIGINT\n");
printf("\tprogram_no : 2 => \t SLONG-INTEGER\n");
printf("\tprogram_no : 3 => \t CHAR-CHAR\n");
printf("\tprogram_no : 4 => \t CHAR-NUMERIC\n");
printf("\tprogram_no : 5 => \t DOUBLE-DOUBLE\n");
exit(1);
}
/* 프로그램 시작시간 및 종료시간 체크 */
long logMsec(const char *astr)
{
struct timeval tv;
struct tm *ctm;
gettimeofday(&tv,NULL);
ctm = localtime(&(tv.tv_sec));
fprintf(stderr, "%s [%.02d:%.02d:%.02d]\n", astr, ctm->tm_hour,
ctm->tm_min, ctm->tm_sec);
return tv.tv_usec;
}
/* Altibase 접속 구문 */
void conn(char *port, char *conntype)
{
char connStr[200];
char query[200];
if (SQL_ERROR == SQLAllocEnv(&env))
{
fprintf(stderr, "SQLAllocEnv error!!\n");
//Environment를 위한 메모리를 할당.
return;
}
if (SQL_ERROR == SQLAllocConnect(env, &con))
// connection을 위한 메모리를 할당
{
fprintf(stderr, "SQLAllocConnect error!!\n");
SQLFreeEnv(env);
return;
}
sprintf((char*)connStr, "DSN=127.0.0.1;PORT_NO=%s;UID=SYS;PWD=MANAGER;CONNTYPE=%s", port, conntype);
/* Connection 생성 */
if (SQL_ERROR == SQLDriverConnect(con, NULL, (char*)connStr,
SQL_NTS, NULL, 0, NULL, SQL_DRIVER_NOPROMPT))
{
if (SQL_SUCCESS == SQLError(env, con, NULL, NULL, &errNo, (char*)errMsg, MSG_LEN, &msgLength))
{
fprintf(stderr, " rCM_-%d : %s\n", errNo, errMsg);
}
SQLFreeConnect(con);
SQLFreeEnv(env);
return;
}
/* 각 SQL문을 실행 시 자동으로 반영하지 않는다 */
SQLSetConnectAttr(con, SQL_ATTR_AUTOCOMMIT,
(void*)SQL_AUTOCOMMIT_OFF, 0);
if (rc == SQL_ERROR)
{
if (SQL_SUCCESS == SQLError(env, con, NULL, NULL, &errNo, (char*)errMsg, MSG_LEN, &msgLength))
{
fprintf(stderr, "[%d : %s]\n", errNo, errMsg);
}
}
hstmt = bstmt = SQL_NULL_HSTMT;
SQLAllocStmt(con, &hstmt);
SQLAllocStmt(con, &bstmt);
/* DDL문을 직접 수행하며 오류발생시 메시지를 지정서식에 따라 파일로 출력 */
strcpy(query, "drop table t1");
rc = SQLExecDirect(hstmt, (char*)query, SQL_NTS);
if (rc == SQL_ERROR)
{
if (SQL_SUCCESS == SQLError(env, con, hstmt, NULL, &errNo, (char*)errMsg, MSG_LEN, &msgLength))
{
fprintf(stderr, "[%d : %s]\n", errNo, errMsg);
}
}
/* DDL문을 직접 수행하며 오류발생시 메시지를 지정서식에 따라 파일로 출력 */
strcpy(query, "create table t1(i1 number(6))");
rc = SQLExecDirect(hstmt, (char*)query, SQL_NTS);
if (rc == SQL_ERROR)
{
if (SQL_SUCCESS == SQLError(env, con, hstmt, NULL, &errNo, (char*)errMsg, MSG_LEN, &msgLength))
{
fprintf(stderr, "[%d : %s]\n", errNo, errMsg);
}
}
}
/* 실행할 SQL문을 준비 */
void prepare()
{
char query[100];
strcpy(query, "insert into t1 values(?)");
rc = SQLPrepare(bstmt, (char*)query, SQL_NTS);
if (rc == SQL_ERROR)
{
if (SQL_SUCCESS == SQLError(env, con, bstmt, NULL, &errNo, (char*)errMsg, MSG_LEN, &msgLength))
{
fprintf(stderr, "[%d : %s]\n", errNo, errMsg);
}
}
}
/* 준비된 SQL문장을 실행 */
void execute()
{
rc = SQLExecute(bstmt);
if (rc == SQL_ERROR)
{
if (SQL_SUCCESS == SQLError(env, con, bstmt, NULL, &errNo, (char*)errMsg, MSG_LEN, &msgLength))
{
fprintf(stderr, "[%d : %s]\n", errNo, errMsg);
}
}
}
void sbigint_bigint(int cnt)
{
int i;
long long i1;
char tmp[100];
int len = SQL_NTS;
prepare();
/* SQL문장에 매개변수(parameter)를 연결시킴 */
SQLBindParameter(bstmt, 1, SQL_PARAM_INPUT, SQL_C_SBIGINT,
SQL_BIGINT, 0, 0, (void*)&i1, 0, &len);
for(i=0; i<cnt; i++)
{
sprintf(tmp, "%d", i);
i1 = atol(tmp);
execute();
}
/* 트랜잭션을 COMMIT 처리 */
rc = SQLTransact(NULL, con, SQL_COMMIT);
}
void slong_integer(int cnt)
{
int i;
int i1;
char tmp[100];
int len = SQL_NTS;
prepare();
/* SQL문장에 매개변수(parameter)를 연결시킴 */
SQLBindParameter(bstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG,
SQL_INTEGER, 0, 0, (void*)&i1, 0, &len);
for(i=0; i<cnt; i++)
{
sprintf(tmp, "%d", i);
i1 = atoi(tmp);
execute();
}
/* 트랜잭션을 COMMIT 처리 */
SQLTransact(NULL, con, SQL_COMMIT);
}
void char_char(int cnt)
{
int i;
char i1[100];
char tmp[100];
int len = SQL_NTS;
prepare();
/* SQL문장에 매개변수(parameter)를 연결시킴 */
SQLBindParameter(bstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, sizeof(i1)-1, 0, (void*)i1, sizeof(i1), &len);
for(i=0; i<cnt; i++)
{
sprintf(tmp, "%d", i);
strcpy(i1, tmp);
execute();
}
/* 트랜잭션을 COMMIT 처리 */
SQLTransact(NULL, con, SQL_COMMIT);
}
void char_number(int cnt)
{
int i;
char i1[100];
char tmp[100];
int len = SQL_NTS;
prepare();
SQLBindParameter(bstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_NUMERIC, sizeof(i1)-1, 0, (void*)i1, sizeof(i1),
&len);
for(i=0; i<cnt; i++)
{
sprintf(tmp, "%d", i);
strcpy(i1, tmp);
execute();
}
/* 트랜잭션을 COMMIT 처리 */
SQLTransact(NULL, con, SQL_COMMIT);
}
void double_double(int cnt)
{
int i;
double i1;
char tmp[100];
int len = SQL_NTS;
prepare();
SQLBindParameter(bstmt, 1, SQL_PARAM_INPUT, SQL_C_DOUBLE,
SQL_DOUBLE, 0, 0, (void*)&i1, 0, &len);
for(i=0; i<cnt; i++)
{
sprintf(tmp, "%d", i);
i1 = atof(tmp);
execute();
}
/* 트랜잭션을 COMMIT 처리 */
SQLTransact(NULL, con, SQL_COMMIT);
}
실행결과#
$ make test
$ ./test 3 20300 1 100
BIGINT - START TIME : [16:43:48]
BIGINT - END TIME : [16:43:49]
JDBC를 활용한 프로그램#
Altibase의 JDBC 드라이버를 이용하여 클라이언트를 작성하는 방법이다. 자세한 내용은 JDBC User's Manual을 참조한다.
JDBC 드라이버#
Altibase는 기본적으로 $ALTIBASE_HOME/lib 디렉토리 안에 Altibase.jar 파일을 JDBC 드라이버로 제공한다.
Altibase 서버와 연결을 설정하기 위해, 먼저 드라이버를 로드하고, URL과의 연결을 시도한다. Altibase JDBC 드라이버에서 지원하는 URL 형식은 다음과 같다.
jdbc:Altibase://hostname:portnum/databasename
JDBC 드라이버를 로드할 때에는 프로그램 내에서 다음과 같은 코드로 JDBC 드라이버를 등록하여 사용한다.
Class.forName("Altibase.jdbc.driver.AltibaseDriver")
Altibase의 URL을 제공하고, URL과 연결을 시도하기 위한 일반적인 방법으로 다음과 같다. (데이터베이스에 로그인 하기 위한 id가 "SYS"이고, 패스워드가 "MANAGER"인 경우)
String url = "jdbc:Altibase://127.0.0.1:20300/mydb";
Connection con = DriverManager.getConnection(url, "SYS", "MANAGER");
CLASSPATH#
JDBC 응용 프로그램의 수행을 위해 Altibase.jar파일이 CLASSPATH에 포함되어야 한다.
$ export CLASSPATH=$ALTIBASE_HOME/lib/Altibase.jar:$CLASSPATH
프로그램 작성#
JDBC를 이용하여 작성하는 프로그램에서 Altibase 서버에 접속하고 종료하는 방법은 대략 다음과 같다.
JDBC 프로그램의 예#
/* SimpleSQL.java */
import java.util.Properties;
import java.sql.*;
class SimpleSQL
{
public static void main(String args[]) {
Properties sProps = new Properties();
Connection sCon = null;
Statement sStmt = null;
PreparedStatement sPreStmt = null;
ResultSet sRS;
if ( args.length == 0 )
{
System.err.println("Usage : java class_name port_no");
System.exit(-1);
}
String sPort = args[0];
String sURL = "jdbc:Altibase://127.0.0.1:" + sPort + "/mydb";
String sUser = "SYS";
String sPassword = "MANAGER";
sProps.put( "user", sUser);
sProps.put( "password", sPassword);
/* Altibase JDBC 드라이버 등록 */
try
{
Class.forName("Altibase.jdbc.driver.AltibaseDriver");
}
catch ( Exception e )
{
System.out.println("Can't register Altibase Driver");
System.out.println( "ERROR MESSAGE : " + e.getMessage() );
System.exit(-1);
}
/* 접속하고 Statment를 할당 */
try
{
sCon = DriverManager.getConnection( sURL, sProps );
sStmt = sCon.createStatement();
}
catch ( Exception e )
{
System.out.println( "ERROR MESSAGE : " + e.getMessage() );
e.printStackTrace();
}
/* 쿼리수행 */
try
{
sStmt.execute( "DROP TABLE TEST_EMP_TBL" );
}
catch ( SQLException e )
{
}
try
{
sStmt.execute( "CREATE TABLE TEST_EMP_TBL " +
"( EMP_FIRST VARCHAR(20), " +
"EMP_LAST VARCHAR(20), " +
"EMP_NO INTEGER )" );
sPreStmt = sCon.prepareStatement( "INSERT INTO TEST_EMP_TBL " +
"VALUES( ?, ?, ? )" );
sPreStmt.setString( 1, "Susan" );
sPreStmt.setString( 2, "Davenport" );
sPreStmt.setInt( 3, 2 );
sPreStmt.execute();
sPreStmt.setString( 1, "Ken" );
sPreStmt.setString( 2, "Kobain" );
sPreStmt.setInt( 3, 3 );
sPreStmt.execute();
sPreStmt.setString( 1, "Aaron" );
sPreStmt.setString( 2, "Foster" );
sPreStmt.setInt( 3, 4 );
sPreStmt.execute();
sPreStmt.setString( 1, "Farhad" );
sPreStmt.setString( 2, "Ghorbani" );
sPreStmt.setInt( 3, 5 );
sPreStmt.execute();
sPreStmt.setString( 1, "Ryu" );
sPreStmt.setString( 2, "Momoi" );
sPreStmt.setInt( 3, 6 );
sPreStmt.execute();
sRS = sStmt.executeQuery( "SELECT EMP_FIRST, EMP_LAST," +
" EMP_NO FROM TEST_EMP_TBL " );
/* 결과를 받아 화면에 출력 */
while( sRS.next() )
{
System.out.println( " EmpName : " + sRS.getString(1) +
" " + sRS.getString(2) );
System.out.println( " EmpNO : " + sRS.getInt(3) );
}
/* 연결 해제 */
sStmt.close();
sPreStmt.close();
sCon.close();
}
catch ( SQLException e )
{
System.out.println( "ERROR CODE : " + e.getErrorCode() );
System.out.println( "ERROR MESSAGE : " + e.getMessage() );
e.printStackTrace();
}
}
}
실행결과#
$ javac SimpleSQL.java
$ java SimpleSQL 20300
EmpName : Susan Davenport
EmpNO : 2
EmpName : Ken Kobain
EmpNO : 3
EmpName : Aaron Foster
EmpNO : 4
EmpName : Farhad Ghorbani
EmpNO : 5
EmpName : Ryu Momoi
EmpNO : 6
C/C++ Precompiler를 활용한 프로그램#
C/C++ Precompiler는 내장 SQL문이 포함된 소스 프로그램을 입력으로 받아, 내장 SQL문들을 실행 시간 라이브러리 호출로 변환하여, 호스트 언어로 컴파일될 수 있는 수정된 소스 프로그램을 생성한다. 자세한 내용은 Precompiler User's Manual을 참조한다.
환경 설정#
C/C++ Precompiler로 전처리된 결과 파일을 컴파일 및 링크하기 위해서 필요한 환경 설정은 다음과 같다.
헤더파일#
전처리된 소스파일을 컴파일하는데 필요한 헤더파일은 ulpLibInterface.h 이며, $ALTIBASE_HOME/include 디렉토리에 있다.
전처리된 응용 프로그램을 컴파일하기 위해서 아래의 옵션을 사용하라:
-I$ALTIBASE_HOME/include
라이브러리#
필요한 라이브러리는 libapre.a와 libodbccli.a이며, $ALTIBASE_HOME/lib 디렉토리에 있다.
전처리된 응용 프로그램을 링크하기 위해서 아래의 옵션을 사용하라:
-L$ALTIBASE_HOME/lib -lapre -lodbccli -lpthread
전처리 실행#
C/C++ Precompiler는 내장 SQL문을 포함하는 C 또는 C++로 작성된 프로그램을 전처리하여 변환된 C 또는 C++ 프로그램을 생성한다. 입력 파일은 .sc 확장자를 가지는 C 또는 C++로 작성된 프로그램이며, 출력 파일은 .c 또는 .cpp를 확장자를 가진다. 출력 파일의 확장자는 사용자가 지정할 수 있으며 기본적으로 .c이다.
실행 옵션#
다음은 apre의 도움말 출력 결과이다. apre에 사용할 수 있는 다양한 전처리 옵션을 보여준다.
$ apre -h
=====================================================================
APRE (Altibase Precompiler) C/C++ Precompiler HELP Screen
=====================================================================
Usage : apre [<options>] <filename>
-h : Display this help information.
-t <c|cpp> : Specify the file extension for the output file.
c - File extension is '.c' (default)
cpp - File extension is '.cpp'
-o <output_path> : Specify the directory path for the output file.
(default : current directory)
-mt : When precompiling a multithreaded application,
this option must be specified.
-I<include_path> : Specify the directory paths for files included using APRE C/C++.
(default : current directory)
-parse <none|partial|full>
: Control which non-SQL code is parsed.
-D<define_name> : Use to define a preprocessor symbol.
-v : Output the version of APRE.
-n : Specify when CHAR variables are not null-padded.
-unsafe_null : Specify to suppress errors when NULL values are fetched
and indicator variables are not used.
-align : Specify when using alignment in AIX.
-spill <values> : Specify the register allocation spill area size.
-keyword : Display all reserved keywords.
-debug <macro|symbol>
: Use for debugging.
macro - Display macro table.
symbol - Display symbol table.
-nchar_var <variable_name_list>
: Process the specified variables using
the Altibase national character set.
-nchar_utf16 : Set client nchar encoding to UTF-16.
-lines : Add #line directives to the generated code.
-silent : No display Copyright
======================================================================
멀티 쓰레드 프로그램#
C/C++ Precompiler는 멀티 쓰레드 프로그램을 지원한다. 멀티 쓰레드 프로그래밍시 내장 SQL문 사용 방법 및 주의 사항에 대해 알아보기로 한다.
-
전처리 시 멀티 쓰레드 프로그램의 판단 근거를 전처리기에게 옵션(-mt)으로 제공하여야 한다.
-
각 쓰레드마다 각각의 연결(connection)이 있어야 한다. 즉, 여러 쓰레드가 하나의 연결을 공유할 수 없다.
-
연결 이름을 가지지 않는 connection(즉, default connection)은 한 프로그램 내에 하나만 허용된다.
-
내장 SQL문에서도 사용할 연결 이름을 명시하여야 한다.
프로그램 작성#
Precompiler 응용 프로그램의 예#
/******************************************************************
* SAMPLE : DELETE
* 1. Using scalar host variables
* 2. Reference : array host variables - arrays1.sc
******************************************************************/
int main()
{
/* 호스트 변수 선언 */
EXEC SQL BEGIN DECLARE SECTION;
char usr[10];
char pwd[10];
char conn_opt[1024];
/* 스칼라형 */
int s_eno;
short s_dno;
EXEC SQL END DECLARE SECTION;
printf("<DELETE>\n");
/* 이름, 비밀번호, 옵션 설정 */
/* set username */
strcpy(usr, "SYS");
/* set password */
strcpy(pwd, "MANAGER");
/* set various options */
strcpy(conn_opt, "Server=127.0.0.1"); /* Port=20300 */
/* Altibase 서버에 접속 */
EXEC SQL CONNECT :usr IDENTIFIED BY :pwd USING :conn_opt;
/* check sqlca.sqlcode */
if (sqlca.sqlcode != SQL_SUCCESS)
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
exit(1);
}
/* 스칼라 호스트 변수 사용 */
s_eno = 5;
s_dno = 1000;
EXEC SQL DELETE FROM EMPLOYEES
WHERE ENO > :s_eno AND DNO > :s_dno AND EMP_JOB LIKE 'P%';
printf("------------------------------------------------------------------\n");
printf("[Scalar Host Variables]\n");
printf("------------------------------------------------------------------\n");
/* sqlca.sqlcode 검사 */
if (sqlca.sqlcode == SQL_SUCCESS)
{
/* sqlca.sqlerrd[2] holds the rows-processed(deleted) count */
printf("%d rows deleted\n\n", sqlca.sqlerrd[2]);
}
else
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
}
/* 접속 해제 */
EXEC SQL DISCONNECT;
/* check sqlca.sqlcode */
if (sqlca.sqlcode != SQL_SUCCESS)
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
}
}
실행결과#
$ make delete
$ ./delete
<DELETE>
------------------------------------------------------------------
[Scalar Host Variables]
------------------------------------------------------------------
3 rows deleted