5. 호스트 변수 데이터 타입#
개요#
호스트 변수는 용도와 기능, 선언 방법 등에서 C 또는 C++ 프로그램에서의 일반 변수와 구분된다. 따라서 호스트 변수의 데이터 타입 또한 일반 변수의 데이터 타입과 차이가 있다. 본 장에서는 다음의 내용들에 대해서 설명한다.
-
호스트 변수의 데이터 타입으로 사용할 수 있는 데이터 타입
-
내장 SQL문이 제공하는 확장된 데이터 타입
-
칼럼 타입과 호스트 변수 타입과의 관계
용어 설명#
여기에서는 본 장에서 사용될 몇 가지 용어에 대하여 설명한다. 앞으로 사용될 용어들의 의미를 정확히 이해하여 본 장의 내용을 이해하는데 도움이 되길 바란다.
호스트 변수#
호스트 변수는 호스트 변수 선언부에 선언하고 내장 SQL문에서 사용하는 변수를 말한다.
일반 변수#
C 또는 C++ 프로그램에서 선언되고 사용되는 모든 변수를 말한다. 본 장에서는 개발자의 이해를 돕기 위해 호스트 변수와의 비교 대상 로 사용된다.
호스트 변수 타입(또는 호스트 변수 데이터 타입)#
호스트 변수 타입은 호스트 변수의 데이터 타입으로, C 또는 C++에서 사용되는 대부분의 데이터 타입과 내장 SQL문에서 제공하는 확장된 데이터 타입 등을 포함한다.
일반 변수 타입(또는 일반 변수 데이터 타입)#
일반 변수 타입은 C 또는 C++ 프로그램에서 사용하는 데이터 타입으로 본 장에서는 개발자의 이해를 돕기 위해 호스트 변수 타입과의 비교 대상으로 사용된다.
칼럼 타입#
칼럼 타입은 데이터베이스 서버의 테이블에 정의된 칼럼의 데이터 타입으로, 호스트 변수의 데이터 타입 결정 시 대응되는 칼럼 타입을 고려하여 호환 가능한 데이터 타입을 사용하여야 한다.
호스트 변수 데이터 타입#
다음은 호스트 변수의 데이터 타입으로 사용할 수 있는 데이터 타입들이다.
-
C 또는 C++에서 사용하는 대부분의 데이터 타입
-
내장 SQL문이 제공하는 확장된 데이터 타입
-
호스트 변수 선언부에서 타입 선언된 데이터 타입
일반 데이터 타입#
호스트 변수의 데이터 타입으로 C 또는 C++에서 지원하는 대부분의 데이터 타입을 사용할 수 있다. 여기에서는 호스트 변수의 데이터 타입으로 사용 가능한 일반 데이터 타입들에 대해 설명한다.
숫자형 타입#
다음은 호스트 변수의 데이터 타입으로 사용 가능한 숫자형 타입들이다.
정수 타입#
int, short int, long int, short, long, long long, unsigned int, unsigned short int, unsigned long int, unsigned short, unsigned long, unsigned long long
실수 타입#
float, double
제한 사항#
long double 타입은 지원하지 않는다. 따라서 호스트 변수의 데이터 타입으로 long double 타입은 사용할 수 없다.
문자형 타입#
다음은 호스트 변수의 데이터 타입으로 사용 가능한 타입들이다.
문자 타입#
char, unsigned char
주의 사항#
이 타입을 출력 호스트 변수로 사용할 경우 대응되는 칼럼 타입이 CHAR 타입이라면 호스트 변수는 칼럼 크기보다 1 크게 선언하여야 한다. 이유는 CHAR 타입(칼럼 타입)은 고정 길이이므로 칼럼 길이만큼 데이터가 반환되는데, 호스트 변수에는 이 데이터와 함께 마지막에 널문자를 붙여서 저장해야 하기 때문이다. 만약 1 크게 선언하지 않았다면, SELECT문이나 FETCH문 수행 후 sqlca.sqlcode 값은 SQL_SUCCESS가 아니라 SQL_SUCCESS_WITH_INFO 이 될 것이다.
일반적으로 칼럼의 대응되는 호스트 변수를 입출력 호스트 변수 각각 따로 선언하지 않고 하나의 호스트 변수를 사용하는 경우가 많기 때문에, 이 타입을 호스트 변수로 선언할 경우 대응되는 칼럼 크기보다 1 크게 선언하기를 추천한다.
포인터 타입#
호스트 변수로 사용 가능한 대부분의 데이터 타입은 포인터의 베이스 타입으로 사용할 수 있다.
char*#
char*를 호스트 변수의 데이터 타입으로 사용할 수 있다.
함수 인자를 호스트 변수로 사용할 경우 char*를 사용하면 편리하다. 함수 인자의 호스트 변수 사용에 대한 자세한 설명은 3장을 참조하기 바란다.
MAX_CHAR_PTR#
Character string을 가리키는 포인터를 호스트 변수로 사용할 경우, APRE전처리기는 포인터가 참조하는 스크링의 최대 사이즈를 65000이라고 가정한다. 이것은 APRE가 실제로 할당된 크기를 알 수가 없기 때문이다. 그러므로 MAX_CHAR_PTR 매크로에 정의된 사이즈보다 더 작은 사이즈가 할당된 char* type 을 출력 호스트 변수로 사용할 경우, 그 사이즈보다 큰 사이즈의 값이 저장되어 메모리 오류가 발생할 수도 있다.
만약 65000보다 더 크게 선언해야 한다면 char* 타입의 호스트 변수를 내장 SQL문에서 사용하기 전에 MAX_CHAR_PTR 매크로를 원하는 크기로 재정의하면 된다.
MAX_CHAR_PTR매크로를 정의하는 방법은 다음과 같다.
#define MAX_CHAR_PTR 90000
MAX_CHAR_PTR 매크로를 정의한 후에, 정의된 크기만큼 char*의 호스트 변수를 선언하거나 메모리를 할당하여 사용할 수 있다.
구조체 포인터#
구조체의 포인터를 호스트 변수의 데이터 타입으로 사용할 수 있다. 함수의 인자를 호스트 변수로 사용할 경우 구조체의 포인터 타입을 사용하면 편리하며, 함수 인자의 호스트 변수 사용에 대한 자세한 설명은 3장을 참조한다.
포인터로 선언한 후에는 반드시 적절한 메모리 할당을 받아야 한다. 메모리 할당이 되었는지 여부는 APRE 전처리기가 알 수 없으므로 주의해야 한다.
배열 포인터#
1차원 배열을 포인터 타입의 변수에 대입해서 INSERT내장 문의 입력 호스트 변수로 사용할 때, FOR 구문을 사용해서 배열 요소의 크기를 지정해야 한다.
단, 아래에 나열한 타입은 1차원 배열을 가리키는 포인터를 INSERT내장 문에서 FOR 구문과 함께 사용하거나, 2차원 배열을 가리키는 포인터를 호스트 변수로 사용할 때 원하지 않는 결과를 초래하게 된다.
char, varchar, APRE_BINARY, APRE_BINARY2, APRE_BYTES, APRE_NIBBLE, APRE_NUMERIC, APRE_BLOB, APRE_CLOB, APRE_BIT, APRE_VARBYTES
아래는 int타입의 배열과 포인터 변수를 INSERT 내장 문에서 입력 호스트 변수로 사용하는 예제이다.
int sInt[10];
int *sIntptr;
sIntptr = sInt;
EXEC SQL FOR 10 INSERT INTO T2 VALUES ( :sIntptr );
FOR 구문에 대한 자세한 설명은 9장을 참조하기 바란다.
또한 아래와 같이 2차원 배열 포인터를 호스트 변수로 사용할 경우 정상적으로 동작하지 않을 수 있다.
int sInt[2][10];
int (*sIntptr)[10];
sIntptr = sInt;
EXEC SQL FOR 10 INSERT INTO T2 VALUES ( :sIntptr );
예제#
[예제 1] 다음은 char* 타입인 v_ename을 입력 호스트 변수로 사용하는 예를 보여준다.
< 예제 프로그램 : argument.sc >
void ins_employee(int v_eno, char* v_ename, short v_dno)
{
EXEC SQL BEGIN ARGUMENT SECTION;
int v_eno;
char* v_ename;
short v_dno;
EXEC SQL END ARGUMENT SECTION;
EXEC SQL INSERT INTO TODAY_EMPLOYEE
VALUES (:v_eno, :v_ename, :v_dno);
}
[예제 2] 다음은 MAX_CHAR_PTR를 정의하는 예를 보여준다.
#define MAX_CHAR_PTR 90000
EXEC SQL BEGIN DECLARE SECTION;
char* var1;
EXEC SQL END DECLARE SECTION;
또는
EXEC SQL BEGIN DECLARE SECTION;
#define MAX_CHAR_PTR 90000
char* var1;
EXEC SQL END DECLARE SECTION;
[예제 3] 다음은 구조체 포인터를 정의하는 다양한 예를 보여준다.
(1) 일반적인 구조체 포인터의 정의
struct tag1
{
int a;
} *A;
A = (struct tag1*)(malloc(sizeof(struct tag1)));
INSERT INTO T1 VALUES ( :A ); 혹은 INSERT INTO T1 VALUES (:A->a);
(2) 구조체 정의 후 구조체 포인터 정의
struct tag1
{
int a;
};
struct tag1 *A;
A = (struct tag1*)(malloc(sizeof(struct tag1)));
SELECT I1 INTO :A FROM T1; 혹은 SELECT I1 INTO :A->a FROM T1;
(3) 구조체 타입의 자료형 정의 후 포인터 정의
typedef struct tag1
{
int a;
}tag1;
tag1 *A;
A = (tag1*)(malloc(sizeof(tag1)));
SELECT I1 INTO :A FROM T1; 혹은 SELECT I1 INTO :A->a FROM T1;
다음은 구조체 포인터인 vDataT2를 입력 호스트 변수로 사용하는 예를 보여준다.
< 예제 프로그램 : pointer.sc >
EXEC SQL BEGIN DECLARE SECTION;
typedef struct tag
{
char n1[11];
int n2;
}tag;
tag *dataT2;
EXEC SQL END DECLARE SECTION;
void ins_t2(tag* vDataT2)
{
EXEC SQL BEGIN ARGUMENT SECTION;
tag *vDataT2;
EXEC SQL END ARGUMENT SECTION;
EXEC SQL INSERT INTO T2 VALUES (:vDataT2->n1, :vDataT2->n2);
}
구조체 타입#
struct#
호스트 변수의 데이터 타입으로 구조체를 사용할 수 있다.
구조체 타입은 테이블의 전체 칼럼을 검색하거나 삽입하는 경우 내장 SQL문에서 호스트 변수를 일일이 나열하지 않고 구조체 타입의 호스트 변수 하나만 사용하면 되므로 개발의 편의성을 제공한다. 예를 들어 INSERT문의 VALUES절이나 SELECT문의 INTO절에 구조체 타입의 호스트 변수를 사용할 수 있다.
구조체의 배열이나 구성 요소가 배열인 구조체도 호스트 변수의 데이터 타입으로 사용 가능하다. 배열 타입에 대한 자세한 설명은 9장을 참조하기 바란다.
제한 사항#
-
호스트 변수가 구조체이면 지시자 변수도 구조체이어야 한다. 이 때, 두 구조체의 구성 요소 개수도 같아야 한다.
예시:
EXEC SQL BEGIN DECLARE SECTION; struct tag1 { int i1; int i2; } var1; struct tag2 { int i1_ind; int i2_ind; } var1_ind1; struct tag3 { int i1_ind; int i2_ind; int i3_ind; } var1_ind2; EXEC SQL END DECLARE SECTION; EXEC SQL INSERT INTO T1(I1, I2) VALUES (:var1 :var1_ind1); (O) EXEC SQL INSERT INTO T1(I1, I2) VALUES (:var1 :var1_ind2); (X)
-
중첩된 구조체를 호스트 변수로 사용할 수 없다. 즉, 구조체의 구성 요소는 구조체일 수 없다.
예시:
EXEC SQL BEGIN DECLARE SECTION; struct tag1 { int i1; struct tag2 { int i2; int i3; } sub_var; } var1; (X) EXEC SQL END DECLARE SECTION;
-
구조체의 배열일 경우 지시자 변수를 지정할 수 없다. 따라서 구조체의 배열 타입을 출력 호스트 변수로 사용할 경우 반환되는 칼럼값이 NULL이 아님을 보장해야 한다. 왜냐하면 반환되는 칼럼 값이 NULL인데 지시자 변수를 지정하지 않았다면 sqlca.sqlcode값이 SQL_SUCCESS_WITH_INFO이기 때문이다.
예시:
EXEC SQL BEGIN DECLARE SECTION; struct tag1 {int i1; int i2; char i3[11]; } var1[10]; struct tag2 {int i1_ind; int i2_ind; int i3_ind; } var1_ind[10]; EXEC SQL END DECLARE SECTION; EXEC SQL INSERT INTO T1(I1, i2, i3) VALUES (:var1 :var1_ind); (X)
-
SELECT문 또는 FETCH문의 INTO절에 구조체의 배열을 호스트 변수로 사용할 경우 출력 호스트 변수는 하나만 사용할 수 있다. 즉 다른 호스트 변수들과 함께 사용할 수 없다. 따라서 INTO절에 사용할 호스트 변수가 구조체의 배열 타입이라면 이 구조체의 구성 요소 개수는 SELECT절의 칼럼 개수와 동일해야 한다.
마찬가지로 INSERT문의 VALUES절에 구조체의 배열을 호스트 변수로 사용할 경우 입력 호스트 변수는 하나만 사용할 수 있다. 즉 다른 호스트 변수들과 함께 사용할 수 없다. 따라서 VALUES절에 사용할 호스트 변수가 구조체의 배열 타입이라면 이 구조체의 구성 요소 개수는 INSERT문의 칼럼 개수와 동일해야 한다.예시:
EXEC SQL BEGIN DECLARE SECTION; struct tag1 { int i1; int i2; } var1[10]; int var2[10]; EXEC SQL END DECLARE SECTION; EXEC SQL INSERT INTO T1(I1, I2, i3) VALUES (:var1, :var2); (X)
-
마지막 2개의 제한 사항은 호스트 변수가 구조체의 배열일 경우 사용될 모든 호스트 변수를 구조체가 포함해야 한다는 내부적인 규칙 때문이다.
예제#
다음은 구조체 타입의 사용 예를 보여준다.
구조체를 goods이름으로 타입 선언 한 후, goods 타입의 s_goods를 호스트 변수를 선언하고 s_goods를 INSERT문의 입력 호스트 변수로 사용한다.
< 예제 프로그램 : hostvar.h >
EXEC SQL BEGIN DECLARE SECTION;
typedef struct goods
{
char gno[10+1];
char gname[20+1];
char goods_location[9+1];
int stock;
double price;
} goods;
EXEC SQL END DECLARE SECTION;
< 예제 프로그램 : insert.sc >
/* specify path of header file */
EXEC SQL OPTION (INCLUDE=./include);
/* include header file for precompile */
EXEC SQL INCLUDE hostvar.h;
EXEC SQL BEGIN DECLARE SECTION;
goods s_goods;
EXEC SQL END DECLARE SECTION;
strcpy(s_goods.gno, "F111100003");
strcpy(s_goods.gname, "XX-102");
strcpy(s_goods.goods_location, "AD0003");
s_goods.stock = 6000;
s_goods.price = 10200.96;
EXEC SQL INSERT INTO GOODS VALUES (:s_goods);
확장된 데이터 타입#
C 또는 C++ 에서 지원하는 데이터 타입 외에 APRE는 확장된 데이터 타입도 호스트 변수로 사용하도록 제공한다. 여기에서는 확장된 데이터 타입들의 종류와 사용 방법에 대해 설명한다.
VARCHAR#
varchar#
대소문자 구별없이 varchar와 VARCHAR가 모두 허용되며, 내부적으로 구조체 타입이다. 예를 들어
varchar a[10];
와 같이 선언하였다면 내부적으로
struct { int len; char arr[10] ;}a;
와 같은 구조를 가진다. 따라서, varchar 타입의 변수값을 참조하려면, a.arr과 같이 varchar 타입의 구성 요소를 명시해야 한다.
varchar 타입은 내부적으로 지시자 변수를 포함하고 있다. 구성 요소 len이 지시자 변수 역할을 한다. 따라서 지시자 변수를 필요로 할 때 varchar 타입을 사용하면 별도의 지시자 변수를 지정하지 않아도 되므로 편리하게 사용할 수 있다.
varchar 타입은 내부적으로 지시자 변수를 포함하고 있지만 별도의 지시자 변수 지정도 가능하다. 이것은 varchar 타입이 구조체의 구성요소 일 경우, 그 구조체에 대응되는 구조체 타입의 지시자 변수 선언 시 구조체 멤버인 varchar 타입에 대응되는 지시자 변수가 구조체 안에 포함되는 것을 가능하게 해준다.
장점#
이 타입은 지시자 변수를 포함하고 있으므로 별도의 지시자 변수 지정이 필요 없다. 따라서 지시자 변수를 필요로 할 경우 사용하면 편리하다.
제한 사항#
-
이 타입은 별도의 지시자 변수 지정이 없으면 자동으로 구성 요소 len이 지시자 변수의 역할을 한다. 따라서 입력 호스트 변수로 사용할 경우 별도의 지시자 변수를 지정하지 않았다면 len의 값을 반드시 적절하게 지정하여야 한다. 입력값을 NULL로 지정하고 싶다면 len에는 -1을, 그렇지 않으면 입력값(arr값)의 길이를 지정한다.
예시:
EXEC SQL BEGIN DECLARE SECTION; varchar var1; EXEC SQL END DECLARE SECTION; strcpy(var1.arr, "ABC"); var1.len = strlen(var1.arr); EXEC SQL INSERT INTO T1(I1) VALUES (:var1); (O)
-
이 타입을 출력 호스트 변수로 사용할 경우 대응되는 칼럼 타입이 CHAR 타입이라면 호스트 변수는 칼럼 크기보다 1 크게 선언하여야 한다. 이유는 CHAR 타입(칼럼 타입)은 고정 길이이므로 칼럼 길이만큼 데이터가 반환되는데, 호스트 변수에는 이와 함께 마지막에 널문자를 저장해야 하기 때문이다. 만약 1 크게 선언하지 않았다면 SELECT문이나 FETCH문 수행 후 sqlca.sqlcode 값은 SQL_SUCCESS_WITH_INFO 이 되 것이다.
-
SELECT문 또는 FETCH문의 INTO절에 varchar의 이차원 배열을 호스트 변수로 사용할 경우 출력 호스트 변수는 하나만 사용할 수 있다. 즉, 다른 호스트 변수들과 함께 사용할 수 없다. 따라서 INTO절에 varchar타입의 이차원 배열을 사용한다면 SELECT절의 칼럼 개수는 1개여야 한다. 마찬가지로 INSERT문의 VALUES절에 varchar타입의 이차원 배열을 호스트 변수로 사용할 경우 입력 호스트 변수는 하나만 사용할 수 있다. 즉, 다른 호스트 변수들과 함께 사용할 수 없다. 따라서 VALUES절에 varchar타입의 이차원 배열을 사용한다면 INSERT문의 칼럼 개수는 1개여야 한다.
이것은 varchar가 내부적으로 구조체이므로 구조체의 제한 사항이 적용된 것이다.예시:
EXEC SQL BEGIN DECLARE SECTION; varchar var1[10][10+1]; int var2[10]; EXEC SQL END DECLARE SECTION; EXEC SQL INSERT INTO T1(I1, I2) VALUES (:var1, :var2); (X)
예제#
다음은 varchar 타입의 사용 예를 보여준다.
varchar 타입의 호스트 변수를 입력 호스트 변수와 출력 호스트 변수로 사용한다. s_cus_job을 입력 호스트 변수로 s_address를 출력 호스트 변수로 사용한다. s_cus_job.len에는 s_cus_job.arr의 데이터 길이를 지정한다. SELECT문 수행 후 s_address.len 값이 -1인지 검사한다.
< 예제 프로그램 : varchar.sc >
EXEC SQL BEGIN DECLARE SECTION;
char s_cname[20+1];
varchar s_cus_job[20+1];
varchar s_address[60+1];
EXEC SQL END DECLARE SECTION;
strcpy(s_cus_job.arr, "WEBMASTER");
s_cus_job.len = strlen(s_cus_job.arr);
EXEC SQL SELECT CNAME, ADDRESS
INTO :s_cname, :s_address
FROM CUSTOMERS
WHERE CNO = BIGINT'7'
AND CUS_JOB = :s_cus_job;
날짜형 타입#
날짜형 타입은 칼럼 타입이 date일 경우에만 사용 가능하다.
3가지의 날짜형 타입이 제공되므로 개발자는 용도에 맞게 사용하면 된다.
SQL_DATE_STRUCT#
이 타입은 년, 월, 일로 구성 되어 있다. 이 타입의 구조는 다음과 같다.
typedef struct tagDATE_STRUCT {
SQLSMALLINT year;
SQLSMALLINT month;
SQLSMALLINT day;
} DATE_STRUCT;
예제#
다음은 SQL_DATE_STRUCT 타입의 사용 예를 보여준다.
s_date를 입력 또는 출력 호스트 변수로 사용하는 예이다.
< 예제 프로그램 : date.sc >
EXEC SQL BEGIN DECLARE SECTION;
SQL_DATE_STRUCT s_date;
int s_ind;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT JOIN_DATE
INTO :s_date :s_ind
FROM EMPLOYEES
WHERE ENO = 3;
s_date.year = 2003;
s_date.month = 5;
s_date.day = 9;
EXEC SQL UPDATE EMPLOYEES
SET JOIN_DATE = :s_date
WHERE ENO = 3;
SQL_TIME_STRUCT#
이 타입 사용 시 시, 분, 초 값을 참조할 수 있다. 이 타입의 구조는 다음과 같다.
typedef struct tagTIME_STRUCT {
SQLSMALLINT hour;
SQLSMALLINT minute;
SQLSMALLINT second;
} TIME_STRUCT;
예제#
다음은 SQL_TIME_STRUCT 타입의 사용 예를 보여준다.
s_time을 입력 또는 출력 호스트 변수로 사용하는 예이다.
< 예제 프로그램 : date.sc >
EXEC SQL BEGIN DECLARE SECTION;
SQL_TIME_STRUCT s_time;
int s_ind;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT JOIN_DATE
INTO :s_time :s_ind
FROM EMPLOYEES
WHERE ENO = 3;
s_time.hour = 12;
s_time.minute = 12;
s_time.second = 12;
EXEC SQL UPDATE EMPLOYEES
SET JOIN_DATE = :s_time
WHERE ENO = 4;
SQL_TIMESTAMP_STRUCT#
이 타입 사용 시 년, 월, 일, 시, 분, 초 값을 참조할 수 있다. 이 타입의 구조는 다음과 같다. 이 구조체 참조 시에 fraction은 10억분의 1초(Nano second)임에 주의 한다.
typedef struct tagTIMESTAMP_STRUCT {
SQLSMALLINT year;
SQLSMALLINT month;
SQLSMALLINT day;
SQLSMALLINT hour;
SQLSMALLINT minute;
SQLSMALLINT second;
SQLINTEGER fraction;
} TIMESTAMP_STRUCT;
예제#
다음은 SQL_TIMESTAMP_STRUCT 타입의 사용 예를 보여준다.
s_timestamp를 입력 또는 출력 호스트 변수로 사용하는 예이다.
< 예제 프로그램 : date.sc >
EXEC SQL BEGIN DECLARE SECTION;
SQL_TIMESTAMP_STRUCT s_timestamp;
int s_ind;
EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT JOIN_DATE
INTO :s_timestamp :s_ind
FROM EMPLOYEES
WHERE ENO = 3;
s_timestamp.year = 2003;
s_timestamp.month = 5;
s_timestamp.day = 9;
s_timestamp.hour = 4;
s_timestamp.minute = 0;
s_timestamp.second = 15;
s_timestamp.fraction = 100000;
EXEC SQL UPDATE EMPLOYEES
SET JOIN_DATE = :s_timestamp
WHERE ENO = 5;
실수형 타입#
SQL_NUMERIC_STRUCT#
이 타입 사용 시 좀 더 정확한 NUMERIC 데이터를 전달할 수 있다.
이 타입의 구조는 다음과 같다.
typedef struct tagSQL_NUMERIC_STRUCT
{
SQLCHAR precision;
SQLSCHAR scale;
SQLCHAR sign; /* 1=pos 0=neg */
SQLCHAR val[SQL_MAX_NUMERIC_LEN];
} SQL_NUMERIC_STRUCT;
이 구조체 참조시에 SQLCHAR val는 little endian byte order를 기반으로 데이터가 처리된다. 따라서 big endian OS를 사용하는경우에도 little endian byte order로 세팅해 줘야 된다.
예제#
다음은 SQL_NUMERIC_STRUCT 타입의 사용 예를 보여준다.
s_price를 입력 또는 출력 호스트 변수로 사용하는 예이다.
< 예제 프로그램 : numeric.sc >
/* declare host variables */
EXEC SQL BEGIN DECLARE SECTION;
char s_gno[10+1];
char s_gname[20+1];
char s_goods_location[9+1];
int s_stock;
SQL_NUMERIC_STRUCT s_price;
EXEC SQL END DECLARE SECTION;
/* use scalar host variables */
strcpy(s_gno, "F111100002");
strcpy(s_gname, "XX-101");
strcpy(s_goods_location, "FD0003");
s_stock = 5000;
/* set value 123.4 on SQL_NUMERIC_STRUCT */
memset(&s_price, 0, sizeof(s_price));
s_price.precision = 4;
s_price.scale = 1;
s_price.sign = 1;
/* set value 1234 to little endian */
s_price.val[0] = 0xD2;
s_price.val[1] = 0x04;
printf("------------------------------------------------------------------\n");
printf("[SQL_NUMERIC_STRUCT Insert]\n");
printf("------------------------------------------------------------------\n");
EXEC SQL INSERT INTO GOODS VALUES (:s_gno, :s_gname, :s_goods_location, :s_stock, :s_price);
memset(s_gname, 0, sizeof(s_gname));
memset(s_goods_location, 0, sizeof(s_goods_location));
s_stock = 0;
memset(&s_price, 0, sizeof(s_price));
printf("------------------------------------------------------------------\n");
printf("[SQL_NUMERIC_STRUCT Select]\n");
printf("------------------------------------------------------------------\n");
EXEC SQL SELECT GNAME, GOODS_LOCATION, STOCK, PRICE INTO :s_gname, :s_goods_location, :s_stock, :s_price FROM GOODS WHERE GNO = :s_gno;
/* check sqlca.sqlcode */
if (sqlca.sqlcode == SQL_SUCCESS)
{
/* sqlca.sqlerrd[2] holds the rows-processed(inserted) count */
printf("%d rows select s_gno=%s, s_gname=%s, s_goods_location=%s, s_stock=%d, s_price=%.15G \n\n",
sqlca.sqlerrd[2], s_gno, s_gname, s_goods_location, s_stock, APRE_NUMERIC_TO_DOUBLE(s_price));
}
else
{
printf("Error : [%d] %s\n\n", SQLCODE, sqlca.sqlerrm.sqlerrmc);
}
이진 타입#
이진 타입은 칼럼 타입이 blob, BYTE, NIBBLE일 경우 호스트 변수의 타입으로 사용할 수 있다.
이진 타입은 내부적으로 다음과 같이 타입 정의가 되어있다.
typedef char APRE_CLOB;
typedef char APRE_BLOB;
typedef char APRE_BINARY;
typedef char APRE_BINARY2;
typedef char APRE_BYTES;
typedef char APRE_NIBBLE;
typedef char APRE_VARBYTES;
아래는 각 타입에 대한 설명이다.
APRE_CLOB#
칼럼 타입이 CLOB인 경우에만 사용이 가능하다.
반드시 지시자 변수를 지정해야 한다.
이 타입의 호스트 변수가 입력 호스트 변수로 사용될 경우, NULL을 입력값으로 지정하려면 지시자 변수에는 -1을 지정하고, 그 외의 값(NULL이 아닌 값)을 입력값으로 지정하려면 지시자 변수에는 호스트 변수에 저장된 값의 길이를 지정한다.
이 타입의 호스트 변수가 출력 호스트 변수로 사용될 때에는 지시자 변수가 -1이면 NULL이 반환된 경우이고, 0보다 큰 값이면 지시자 변수에는 호스트 변수에 저장된 값의 길이가 저장된다.
예제#
다음은 APRE_CLOB 타입의 사용 예를 보여준다.
입력 호스트 변수로 ins_clob을, 입력 지시자 변수로 ins_clob_ind를 사용한다. ins_clob_ind에는 ins_clob에 저장된 데이터의 길이를 입력한다. 출력 호스트 변수로 sel_clob을, 출력 지시자 변수로 sel_clob_ind를 사용한다. SELECT문 수행 후 sel_clob_ind에는 sel_clob값이 NULL이면 -1이, 그렇지 않으면 sel_clob의 길이가 저장될 것이다.
< 예제 프로그램 : binary.sc >
EXEC SQL BEGIN DECLARE SECTION;
APRE_CLOB ins_clob[10+1];
APRE_CLOB sel_clob[10+1];
SQLLEN ins_clob_ind;
SQLLEN sel_clob_ind;
EXEC SQL END DECLARE SECTION;
memset(ins_clob, 0x41, 10);
ins_clob_ind = 10; /* set length of ins_clob value to indicator variable */
EXEC SQL INSERT INTO T_CLOB
VALUES (:ins_clob :ins_clob_ind);
EXEC SQL SELECT *
INTO :sel_clob :sel_clob_ind
FROM T_CLOB;
만약 NOT NULL 제약이 있는 CLOB 타입 컬럼에 NULL 값을 INSERT 혹은 UPDATE 시도하면 [Unable to insert (or update) NULL into NOT NULL column.] 에러가 발생한다. 이 경우, Empty로 초기화된 데이터가 남아 있어 반드시 트랜잭션을 롤백해야 한다.
APRE_BLOB#
칼럼 타입이 BLOB 인 경우에만 사용이 가능하다.
반드시 지시자 변수를 지정해야 한다.
이 타입의 호스트 변수가 입력 호스트 변수로 사용될 경우, NULL을 입력값으로 지정하려면 지시자 변수에는 -1을 지정한다. 그 외의 값(NULL이 아닌 값)을 입력값으로 지정하려면 지시자 변수에는 호스트 변수에 저장된 값의 길이를 지정한다.
이 타입의 호스트 변수가 출력 호스트 변수로 사용될 경우, 지시자 변수가 -1이면 NULL이 반환된 경우이고, 0보다 큰 값이면 지시자 변수에는 호스트 변수에 저장된 값의 길이가 저장된다.
예제#
다음은 APRE_BLOB 타입의 사용 예를 보여준다.
입력 호스트 변수로 ins_blob을, 입력 지시자 변수로 ins_blob_ind를 사용한다. ins_blob_ind에는 ins_blob의 길이가 저장된다. 출력 호스트 변수로 sel_blob을, 출력 지시자 변수로 sel_blob_ind를 사용한다. SELECT문 수행 후 sel_blob_ind에는 sel_blob값이 NULL이면 -1이, 그렇지 않으면 sel_blob의 길이가 저장된다.
< 예제 프로그램 : binary.sc >
EXEC SQL BEGIN DECLARE SECTION;
APRE_BLOB ins_blob[10+1];
APRE_BLOB sel_blob[10+1];
SQLLEN ins_blob_ind;
SQLLEN sel_blob_ind;
EXEC SQL END DECLARE SECTION;
memset(ins_blob, 0x21, 10);
ins_blob_ind = 10; /* set length of ins_blob value to indicator variable */
EXEC SQL INSERT INTO T_BLOB
VALUES (:ins_blob :ins_blob_ind);
EXEC SQL SELECT *
INTO :sel_blob :sel_blob_ind
FROM T_BLOB;
만약 NOT NULL 제약이 있는 BLOB 타입 컬럼에 NULL 값을 INSERT 혹은 UPDATE 시도하면 [Unable to insert (or update) NULL into NOT NULL column.] 에러가 발생한다. 이 경우, Empty로 초기화된 데이터가 남아 있어 반드시 트랜잭션을 롤백해야 한다.
APRE_BINARY#
APRE_BLOB 과 동일한 특성을 가진다.
예제#
다음은 APRE_BINARY 타입의 사용 예를 보여준다.
입력 호스트 변수로 ins_blob을, 입력 지시자 변수로 ins_blob_ind를 사용한다. ins_blob_ind에는 ins_blob의 길이가 저장된다. 출력 호스트 변수로 sel_blob을, 출력 지시자 변수로 sel_blob_ind를 사용한다. SELECT문 수행 후 sel_blob_ind에는 sel_blob값이 NULL이면 -1이, 그렇지 않으면 sel_blob의 길이가 저장된다.
< 예제 프로그램 : binary.sc >
EXEC SQL BEGIN DECLARE SECTION;
APRE_BINARY ins_blob[10+1];
APRE_BINARY sel_blob[10+1];
int ins_blob_ind;
int sel_blob_ind;
EXEC SQL END DECLARE SECTION;
memset(ins_blob, 0x21, 10);
ins_blob_ind = 10; /* set length of ins_blob value to indicator variable */
EXEC SQL INSERT INTO T_BLOB
VALUES (:ins_blob :ins_blob_ind);
EXEC SQL SELECT *
INTO :sel_blob :sel_blob_ind
FROM T_BLOB;
APRE_BINARY2#
APRE_BLOB, APRE_BINARY 와 동일한 특성을 가진다. 다만 입력 호스트 변수의 데이터 사이즈가 128KB 이하일 때 성능향상이 있으며, 최대 데이터 사이즈가 100MB라는 제한이 있다. 데이터 사이즈가 128KB를 초과할 때는 성능 향상 및 메모리 절약을 위해 APRE_BLOB, APRE_BINARY 타입 사용을 추천한다.
예제#
다음은 APRE_BINARY2 타입의 사용 예를 보여준다.
입력 호스트 변수로 ins_blob을, 입력 지시자 변수로 ins_blob_ind를 사용한다. ins_blob_ind에는 ins_blob의 길이가 저장된다. 출력 호스트 변수로 sel_blob을, 출력 지시자 변수로 sel_blob_ind를 사용한다. SELECT문 수행 후 sel_blob_ind에는 sel_blob값이 NULL이면 -1이, 그렇지 않으면 sel_blob의 길이가 저장된다.
< 예제 프로그램 : binary.sc >
EXEC SQL BEGIN DECLARE SECTION;
APRE_BINARY2 ins_blob[10+1];
APRE_BINARY2 sel_blob[10+1];
int ins_blob_ind;
int sel_blob_ind;
EXEC SQL END DECLARE SECTION;
memset(ins_blob, 0x21, 10);
ins_blob_ind = 10; /* set length of ins_blob value to indicator variable */
EXEC SQL INSERT INTO T_BLOB
VALUES (:ins_blob :ins_blob_ind);
EXEC SQL SELECT *
INTO :sel_blob :sel_blob_ind
FROM T_BLOB;
APRE_BYTES#
칼럼 타입이 BYTE인 경우에만 사용 가능하다.
그 외 다른 특성은 APRE_BLOB 과 동일하다.
예제#
다음은 APRE_BYTES 타입의 사용 예를 보여준다.
입력 호스트 변수로 ins_bytes를, 입력 지시자 변수로 ins_bytes_ind를 사용한다. ins_bytes_ind에는 ins_bytes의 길이가 저장된다. 출력 호스트 변수로 sel_bytes를, 출력 지시자 변수로 sel_bytes_ind를 사용한다. SELECT문 수행 후 sel_bytes_ind에는 sel_bytes값이 NULL이면 -1이, 그렇지 않으면 sel_bytes의 길이가 저장된다.
< 예제 프로그램 : binary.sc >
EXEC SQL BEGIN DECLARE SECTION;
APRE_BYTES ins_bytes[5+1];
APRE_BYTES sel_bytes[5+1];
int ins_bytes_ind;
int sel_bytes_ind;
EXEC SQL END DECLARE SECTION;
memset(ins_bytes, 0x21, 5);
ins_bytes_ind = 5; /* set length of ins_bytes value to indicator variable */
EXEC SQL INSERT INTO T_BYTES
VALUES (:ins_bytes :ins_bytes_ind);
EXEC SQL SELECT *
INTO :sel_bytes :sel_bytes_ind
FROM T_BYTES;
APRE_NIBBLE#
칼럼 타입이 NIBBLE인 경우에만 사용 가능하다.
입력 호스트 변수의 경우, NULL을 입력값으로 지정하려면 지시자 변수를, 그 외의 값(NULL이 아닌 값)을 입력값으로 지정하려면 호스트 변수의 첫 번째 byte를 이용한다. 이 때, 지시자 변수가 우선한다. 즉, 내부적으로 지시자 변수를 먼저 검사하여 -1이면 NULL로 처리하고 그렇지 않으면 호스트 변수의 첫 번째 byte값을 입력 데이터의 길이로 처리한다. 따라서 NULL을 입력값으로 지정하려면 지시자 변수에 -1을 지정하고, 그 외의 값(NULL이 아닌 값)을 입력값으로 지정하려면 호스트 변수의 첫 번째 byte에 입력 데이터의 길이를 지정한다.
첫 번째 byte에는 입력 데이터의 길이가 저장되므로 실제 데이터는 호스트 변수의 두 번째 byte부터 저장된다. 따라서, 입력 데이터의 길이는 호스트 변수의 두 번째 byte부터 계산되며 nibble수를 의미한다. 1nibble은 4bit이다.
출력 호스트 변수의 경우, 지시자 변수값이 -1이면 NULL이 반환된 경우이고, 그렇지 않으면 지시자 변수에는 호스트 변수에 저장된 데이터의 전체 길이(bytes수)가 저장된다. 그리고 호스트 변수의 첫 번째 byte에는 실제 데이터의 길이(nibbles수, 4bit=1)가 저장되며, 실제 데이터는 두 번째 byte부터 저장된다. 따라서 NULL이 아닌 경우 지시자 변수값과 첫 번째 byte값과의 관계는 다음과 같다.
지시자 변수값 = ((첫번째byte값+1)/2 + 1(첫번째byte))
예제#
다음은 APRE_NIBBLE 타입의 사용 예를 보여준다.
입력 호스트 변수로 ins_nibble을 사용한다. 입력값이 NULL이 아니므로 ins_nibble[0]에 입력 데이터의 길이(nibble수) 10을 지정한다.
출력 호스트 변수로 sel_nibble을, 출력 지시자 변수로 sel_nibble_ind를 사용한다. SELECT문 수행 후 sel_nibble값이 NULL이면 sel_nibble_ind에는 -1이, 그렇지 않으면 sel_nibble의 길이(bytes수)가 저장된다. 그리고 sel_nibble[0]에는 sel_nibble[1]부터 계산된 실제 데이터의 길이(nibble수)가 저장된다.
< 예제 프로그램 : binary.sc >
EXEC SQL BEGIN DECLARE SECTION;
APRE_NIBBLE ins_nibble[5+2];
APRE_NIBBLE sel_nibble[5+2];
int sel_nibble_ind;
EXEC SQL END DECLARE SECTION;
memset(ins_nibble+1, 0x21, 5);
ins_nibble[0] = 10; /* set length of ins_nibble value to ins_nibble[0] */
EXEC SQL INSERT INTO T_NIBBLE
VALUES (:ins_nibble);
EXEC SQL SELECT *
INTO :sel_nibble :sel_nibble_ind
FROM T_NIBBLE;
APRE_VARBYTES#
칼럼 타입이 VARBYTE인 경우에만 사용 가능하다.
그 외 다른 특성은 APRE_BLOB 과 동일하다.
예제 프로그램#
varchar.sc#
$ALTIBASE_HOME/sample/APRE/varchar.sc 참고
실행결과#
$ is -f schema/schema.sql
$ make varchar
$ ./varchar
<VARCHAR TYPE>
-----------------------------------------------------------
[Scalar VARCHAR]
-----------------------------------------------------------
s_cname = [DKHAN ]
s_address.arr = [YeongdeungpoGu Seoul]
s_address.len = [20]
-----------------------------------------------------------
[Array of VARCHAR]
-----------------------------------------------------------
CUS_JOB
-----------------------------------------------------------
ENGINEER
DOCTOR
DESIGNER
ENGINEER
WEBMASTER
WEBPD
PLANER
PD
DESIGNER
NULL
MANAGER
BANKER
ENGINEER
BANKER
MANAGER
PLANER
NULL
ENGINEER
NULL
WEBMASTER
-----------------------------------------------------------
[Structure Included VARCHAR]
-----------------------------------------------------------
Success insert
-----------------------------------------------------------
[Array of Structure Included VARCHAR]
-----------------------------------------------------------
3 rows inserted
3 times insert success
date.sc#
$ALTIBASE_HOME/sample/APRE/date.sc 참고
실행결과#
$ is -f schema/schema.sql
$ make date
$ ./date
<DATE TYPE>
------------------------------------------------------
[SQL_DATE_STRUCT]
------------------------------------------------------
JOIN_DATE of ENO is 3 : 2000/1/11
------------------------------------------------------
[SQL_TIME_STRUCT]
------------------------------------------------------
JOIN_DATE of ENO is 3 : 0:0:0
------------------------------------------------------
[SQL_TIMESTAMP_STRUCT]
------------------------------------------------------
JOIN_DATE of ENO is 3 : 2000/1/11 0:0:0:0
------------------------------------------------------
[SQL_DATE_STRUCT]
------------------------------------------------------
Success update with SQL_DATE_STRUCT
1 rows updated
------------------------------------------------------
[SQL_TIME_STRUCT]
------------------------------------------------------
Success update with SQL_TIME_STRUCT
1 rows updated
------------------------------------------------------
[SQL_TIMESTAMP_STRUCT]
------------------------------------------------------
Success update with SQL_TIMESTAMP_STRUCT
1 rows updated
------------------------------------------------------
[Array of Structure Included Date Type]
------------------------------------------------------
Success insert
3 rows inserted
3 times insert success
binary.sc#
$ALTIBASE_HOME/sample/APRE/binary.sc 참고
실행결과#
$ is -f schema/schema.sql
$ make binary
$ ./binary
<BINARY TYPE>
------------------------------------------------------
[APRE_CLOB]
------------------------------------------------------
Success insert with APRE_CLOB
sel_clob = AAAAAAAAAA
sel_clob_ind = 10
------------------------------------------------------
[APRE_BLOB]
------------------------------------------------------
Success insert with APRE_BLOB
sel_blob = !!!!!!!!!!
sel_blob_ind = 10
------------------------------------------------------
[APRE_BINARY]
------------------------------------------------------
Success insert with APRE_BINARY
sel_blob = !!!!!!!!!!
sel_blob_ind = 10
------------------------------------------------------
[APREBYTES]
------------------------------------------------------
Success insert with APRE_BYTES
sel_bytes = !!!!!
sel_bytes_ind = 5
------------------------------------------------------
[APRE_NIBBLE]
------------------------------------------------------
Success insert with APRE_NIBBLE
sel_nibble = !!!!!
sel_nibble_ind = 6
sel_nibble[0] = 10
칼럼 타입과 호스트 변수 타입#
칼럼 타입마다 다양한 호스트 변수 타입을 사용할 수 있다. 여기에서는 칼럼 타입에 따른 변환 가능한 호스트 변수 타입을 알아보고 그 중에서도 칼럼 타입별 최적의 호스트 변수 타입을 알아보기로 한다.
입력 호스트 변수#
다음 표는 칼럼 타입별 사용 가능한 입력 호스트 변수 타입을 나열한 표이다. 그리고 '최소 변환 비용을 가지는 호스트 변수 타입'은 권장 타입으로써 이 타입을 사용할 때 성능 향상을 기대할 수 있다.
칼럼 타입 | 변환 가능한 호스트 변수 타입 | 최소 변환 비용을 가지는 호스트 변수 타입 | |
---|---|---|---|
문자형 타입 | CHAR | char, varchar, short, int, long, long long, double, float, SQL_DATE_STRUCT, SQL_TIME_STRUCT, SQL_TIMESTAMP_STRUCT, | char, varchar |
VARCHAR | char, varchar, short, int, long, long long, double, float, SQL_DATE_STRUCT, SQL_TIME_STRUCT, SQL_TIMESTAMP_STRUCT, | char, varchar | |
정수형 타입 | SMALLINT | char, varchar, short, int, long, long long, double, float | short |
INTEGER | char, varchar, short, int, long, long long, double, float | int | |
BIGINT | char, varchar, short, int, long, long long, double, float | long, long long | |
실수형 타입 | NUMERIC NUMBER DECIMAL | char, varchar, short, int, long, long long, double, float, SQL_NUMERIC_STRUCT | char, long, long long, float, double |
FLOAT | char, varchar, short, int, long, long long, double, float | float | |
REAL | char, varchar, short, int, long, long long, double, float | double | |
DOUBLE | char, varchar, short, int, long, long long, double, float | double | |
날짜형 타입 | DATE | char, SQL_DATE_STRUCT, SQL_TIME_STRUCT, SQL_TIMESTAMP_STRUCT | char, SQL_DATE_STRUCT, SQL_TIME_STRUCT, SQL_TIMESTAMP_STRUCT |
이진 타입 | CLOB | APRE_CLOB | APRE_CLOB |
BLOB | APRE_BLOB, APRE_BINARY, APRE_BINARY2 | APRE_BLOB, APRE_BINARY, APRE_BINARY2 | |
BYTE | APRE_BYTES, APRE_VARBYTES | APRE_BYTES, APRE_VARBYTES | |
NIBBLE | APRE_NIBFBLE | APRE_NIBBLE | |
VARBYTE | APRE_BYTES, APRE_VARBYTES | APRE_BYTES, APRE_VARBYTES |
출력 호스트 변수#
다음 표는 칼럼 타입별 사용 가능한 출력 호스트 변수 타입을 나열한 표이다. 그리고 '최소 변환 비용을 가지는 호스트 변수 타입'은 권장 타입으로서 이 타입 사용 시 성능 향상을 기대할 수 있다.
칼럼 타입 | 변환 가능한 호스트 변수 타입 | 최소 변환 비용을 가지는 호스트 변수 타입 | |
---|---|---|---|
문자형 타입 | CHAR | char, varchar, APRE_BINARY, APRE_BINARY2 | char, varchar |
VARCHAR | char, varchar, APRE_BINARY, APRE_BINARY2 | char, varchar | |
정수형 타입 | SMALLINT | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2 | short |
INTEGER | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2 | int | |
BIGINT | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2 | long, long long | |
실수형 타입 | NUMERIC NUMBER DECIMAL | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2, SQL_NUMERIC_STRUCT | char, long, long long, float, double |
FLOAT | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2 | float | |
REAL | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2 | double | |
DOUBLE | char, varchar, short, int, long, long long, double, float, APRE_BINARY, APRE_BINARY2 | double | |
날짜형 타입 | DATE | char, SQL_DATE_STRUCT, SQL_TIME_STRUCT, SQL_TIMESTAMP_STRUCT, APRE_BINARY, APRE_BINARY2 | char, SQL_DATE_STRUCT, SQL_TIME_STRUCT, SQL_TIMESTAMP_STRUCT |
이진 타입 | CLOB | APRE_CLOB | APRE_CLOB |
BLOB | APRE_BLOB, APRE_BINARY, APRE_BINARY2 | APRE_BLOB, APRE_BINARY, APRE_BINARY2 | |
BYTE | APRE_BYTES, APRE_VARBYTES, APRE_BINARY, APRE_BINARY2 | APRE_BYTES, APRE_VARBYTES | |
NIBBLE | APRE_NIBBLE, APRE_BINARY, APRE_BINARY2 | APRE_NIBBLE | |
VARBYTE | APRE_BYTES, APRE_VARBYTES, APRE_BINARY, APRE_BINARY2 | APRE_BYTES, APRE_VARBYTES |
출력 호스트 변수의 경우 모든 칼럼 타입에 대해 APRE_BINARY, APRE_BINARY2 타입을 호스트 변수로 사용할 수 있다. APRE_BINARY, APRE_BINARY2 타입은 칼럼값을 해당 타입에 맞게 변환해서 호스트 변수에 저장하는게 아니라 메모리 내용 그대로 호스트 변수에 저장(memcpy)한다. 따라서, APRE_BINARY, APRE_BINARY2 타입을 호스트 변수로 사용하려면 각 칼럼 타입별로 내부적으로 어떻게 메모리에 저장되는지 저장방식을 알아야 하고, 이를 해석할 줄 알아야 한다. 이처럼 APRE_BINARY, APRE_BINARY2 타입을 호스트 변수로 사용할 경우 타입 변환 비용이 없어 성능은 좋을지 모르나 개발자 관점에서 사용하기 복잡하여 일반적으로 APRE_BINARY, APRE_BINARY2 타입은 BLOB 타입(칼럼 타입)을 제외하고는 사용을 추천하지 않는다.