この章は、次の項目で構成されています。
OCIデータ型のマッピング関数および操作関数を使用すると、事前定義済のOracle Cデータ型のインスタンスを操作できます。これらのデータ型は、Oracleのオブジェクト型など、ユーザー定義のデータ型の属性を表現するために使用します。
OCIの各関数グループは、特定のネーミング規則によって区別されます。たとえば、データ型のマッピング関数および操作関数は、関数名が「OCI」接頭辞で始まり、その後にデータ型が続くため、簡単に認識できます(例: OCIDateFromText()およびOCIRawSize())。後述するように、名前はさらに特定の型のデータを操作する関数グループに分類できます。
また、これらの関数を操作する事前定義済のOracle C型は、「OCI」の接頭辞で始まる名前でも区別できます(例: OCIDateやOCIString)。
データ型のマッピング関数および操作関数は、アプリケーションで、Oracleデータベースに格納されたオブジェクト、またはSQL問合せで取り出されたオブジェクトの属性を操作、バインドまたは定義する必要がある場合に使用します。第14章「OCIのオブジェクトに関する高度なトピック」でも説明していますが、取り出したオブジェクトはクライアント側のオブジェクト・キャッシュに格納されます。
この章では、OCIデータ型のマッピング関数および操作関数で操作できる各データ型の用途と構造について説明します。また、関数をグループ別にまとめ、使用可能な関数とその目的のリストを示します。
この章では、OCIアプリケーションのバインド操作と定義操作でこれらのデータ型を使用する方法も説明します。
OCIクライアントは、バインドまたは定義を行う前に記述子を割り当てる必要があります。OCIStmtExecute()およびOCIStmtFetch2()は、記述子がOCIDescriptorAlloc()によって割り当てられていない場合、これらの記述子にはメモリーを割り当てません。
これらの関数は、OCIアプリケーションをオブジェクト・モードで実行している間にのみ有効です。OCIのオブジェクト・モードでの初期化、およびオブジェクトにアクセスして操作するOCIアプリケーションの作成の詳細は、「環境およびオブジェクト・キャッシュの初期化」を参照してください。
|
注意: OCIDateなどのオブジェクト型に対する操作では、結果のアドレスを演算子のアドレスと同じにできます。 |
Oracleには、表を作成し、ユーザー定義のデータ型(オブジェクト型を含む)を指定できる、一連の事前定義済のデータ型があります。オブジェクト型はOracleの機能を拡張するものであり、オブジェクト型を使用すると、処理対象のデータの型を正確にモデル化したデータ型を作成できます。そのため、プログラマは、より効率的で簡単なデータ・アクセスができます。
NCHARおよびNVARCHAR2はオブジェクトの属性として使用でき、CではOCIString *にマップされます。
データベースの表およびオブジェクト型は、Oracleが供給するデータ型に基づいています。データベースの表およびオブジェクト型は、SQL文で作成し、VARCHAR2やNUMBERなどの特定のOracle内部データ型を使用して格納します。たとえば、次のSQL文では、ユーザー定義のaddressデータ型と、そのデータ型のインスタンスを格納するオブジェクト表を作成します。
CREATE TYPE address AS OBJECT (street1 varchar2(50), street2 varchar2(50), city varchar2(30), state char(2), zip number(5)); CREATE TABLE address_table OF address;
この新しいaddress型を使用して、次のようなオブジェクト列を持つ標準表を作成したとします。
CREATE TABLE employees (name varchar2(30), birthday date, home_addr address);
OCIアプリケーションでは、SQL文に対応付けられた、簡単なバインドおよび定義操作を使用して、情報をemployees表のnameおよびbirthdayの列で操作できます。オブジェクトの属性として格納されている情報にアクセスするには、いくつかの追加ステップが必要です。
OCIアプリケーションでは、まずオブジェクトをC言語書式で表現する手段が必要です。そのためには、Object Type Translator(OTT)を使用して、ユーザー定義型のC構造体表現を生成します。生成した構造体の要素には、Oracleのデータ型をC言語にマッピングしたデータ型が含まれます。
その他のC型としてOCIIndがあります。これは、オブジェクト型の属性に対応するNULLインジケータ情報を表現するために使用します。
事前定義済のOracle型についてマッピングを指定する場合、Oracleでは独特の設計方針に従ってきました。カレント・システムには、次のメリットがあります。
OCINumberのようなデータ型の実際の表現は、クライアントのアプリケーションでは不透明で、データ型は事前に定義済の一連の関数によって操作されます。これによって、将来改善する際にも、ユーザー・コードを中断せずに内部表現を変更できます。
オブジェクト指向パラダイムに従った実装がなされています。このパラダイムではクラスの実装の詳細は隠されており、見えるのは必要な操作のみです。
この実装方法はプログラマにとって有利です。Oracleの数値で提供されるOracle数値変数を、精度を落とすことなく操作する場合のCプログラムを考えてみます。これをOracleリリース7で行うには、SELECT ..FROM DUAL文の発行が必要でした。以降のリリースでは、OCINumber*()関数をコールするのみです。
OCIアプリケーションで、2つの整変数を合計して結果を第3の変数に格納するという非常に簡単なデータ操作を行うとします。
int int_1, int_2, sum; ... /* some initialization occurs */ ... sum = int_1 + int_2;
C言語は、integerのような単純な型に対する一連の事前定義済操作を提供します。ただし、表15-1「オブジェクト型属性のオブジェクト・データ型マッピング」にリストしたCデータ型は、単純なCの基本形ではありません。OCIStringおよびOCINumberのような型は、実際は特定のOracle定義の内部構造体を持つ構造体です。単に、2つのOCINumberを合計して、その値を第3の変数に格納することはできません。
次の内容は、有効ではありません。
OCINumber num_1, num_2, sum; ... /* some initialization occurs */ ... sum = num_1 + num_2; /* NOT A VALID OPERATION */
このような新しいデータ型を操作するための関数が、OCIデータ型のマッピング関数および操作関数です。たとえば、この例でOCINumberを加算するには、OCINumberAdd()関数を使用します。
OCINumber num_1, num_2, sum; ... /* some initialization occurs */ ... OCINumberAdd(errhp, &num_1, &num_2, &sum): /* errhp is error handle */
OCIには、新規データ型をそれぞれ操作する関数があります。関数の名前から、その関数で操作するデータ型がわかります。最初の3文字「OCI」は、関数が「OCI」の一部であることを示します。関数名の次の部分は、関数で操作するデータ型を示します。次の表では、各種の関数の接頭辞、関数名の例および関数の操作対象のデータ型を示します。
図12-1 関数の接頭辞の例
| 関数の接頭辞 | 例 | 操作対象 |
|---|---|---|
OCIColl |
OCICollGetElem() |
OCIColl, OCIIter, OCITable, OCIArray |
OCIDate |
OCIDateDaysBetween() |
OCIDate |
OCIDateTime |
OCIDateTimeSubtract() |
OCIDate, OCIDateTime |
OCIInter |
OCIInterToText() |
OCIInterval |
OCIIter |
OCIIterInit() |
OCIIter |
OCINumber |
OCINumberAdd() |
OCINumber |
OCIRaw |
OCIRawResize() |
OCIRaw * |
OCIRef |
OCIRefAssign() |
OCIRef * |
OCIString |
OCIStringSize() |
OCIString * |
OCITable |
OCITableLast() |
OCITable * |
各データ型の構造については、そのデータ型を操作対象にする関数のリストとともに、この章で後述します。
Oracleの日付書式は、不透明なC構造体であるOCIDate型でCにマップされます。構造体の要素は日付の年、月、日、時間、分および秒を表します。適切なOCI関数を使用すると、特定の要素を設定および取出しできます。
OCIDateデータ型は、バインド・コールまたは定義コールで、外部型コードSQLT_ODTを使用して直接バインドまたは定義できます。
ファンクション・コール内の日付という用語は、指定がないかぎり、OCIDate型の値を指します。
次のコード例では、OCIコールを使用して、OCIDate型の属性を操作する方法を示します。この例では、OCIEnvおよびOCIErrorは、第2章「OCIプログラミングの基本」の説明に従って初期化されているとします。確保については、第14章「OCIのオブジェクトに関する高度なトピック」を参照してください。
#define FMT "DAY, MONTH DD, YYYY"
#define LANG "American"
struct person
{
OCIDate start_date;
};
typedef struct person person;
OCIError *err;
person *tim;
sword status; /* error status */
uword invalid;
OCIDate last_day, next_day;
text buf[100], last_day_buf[100], next_day_buf[100];
ub4 buflen = sizeof(buf);
/* Pin tim person object in the object cache. */
/* For this example, assume that
/* tim is pointing to the pinned object. */
/* set the start date of tim */
OCIDateSetTime(&tim->start_date,8,0,0);
OCIDateSetDate(&tim->start_date,1990,10,5);
/* check if the date is valid */
if (OCIDateCheck(err, &tim->start_date, &invalid) != OCI_SUCCESS)
/* error handling code */
if (invalid)
/* error handling code */
/* get the last day of start_date's month */
if (OCIDateLastDay(err, &tim->start_date, &last_day) != OCI_SUCCESS)
/* error handling code */
/* get date of next named day */
if (OCIDateNextDay(err, &tim->start_date, "Wednesday", strlen("Wednesday"),
&next_day) != OCI_SUCCESS)
/* error handling code */
/* convert dates to strings and print the information out */
/* first convert the date itself*/
buflen = sizeof(buf);
if (OCIDateToText(err, &tim->start_date, FMT, sizeof(FMT)-1, LANG,
sizeof(LANG)-1, &buflen, buf) != OCI_SUCCESS)
/* error handling code */
/* now the last day of the month */
buflen = sizeof(last_day_buf);
if (OCIDateToText(err, &last_day, FMT, sizeof(FMT)-1, LANG, sizeof(LANG)-1,
&buflen, last_day_buf) != OCI_SUCCESS)
/* error handling code */
/* now the first Wednesday after this date */
buflen = sizeof(next_day_buf);
if (OCIDateToText(err, &next_day, FMT, sizeof(FMT)-1, LANG,
sizeof(LANG)-1, &buflen, next_day_buf) != OCI_SUCCESS)
/* error handling code */
/* print out the info */
printf("For: %s\n", buf);
printf("The last day of the month is: %s\n", last_day_buf);
printf("The next Wednesday is: %s\n", next_day_buf);
出力は次のようになります。
For: FRIDAY , OCTOBER 05, 1990 The last day of the month is: WEDNESDAY, OCTOBER 31, 1990 The next Wednesday is: WEDNESDAY, OCTOBER 10, 1990
OCIDateTimeデータ型は、Oracleのタイムとタイムスタンプのデータ型(TIME、TIME WITH TIME ZONE、TIMESTAMP、TIMESTAMP WITH TIME ZONE)およびANSI DATEデータ型の表現に使用する不透明な構造体です。適切なOCI関数を使用すると、これらの型(つまり、年、日、小数秒)でのデータの設定または取出しを実行できます。
OCIIntervalデータ型も不透明な構造体で、Oracleの時間隔データ型(INTERVAL YEAR TO MONTH、INTERVAL DAY TO SECOND)の表現に使用されます。
バインドまたは定義のコールで、次の外部型コードを使用すると、OCIDateTimeとOCIIntervalのデータをバインドおよび定義できます。
表12-2 日時および時間隔データ型のバインドと定義
| OCIデータ型 | データの型 | バンド/定義の外部型コード |
|---|---|---|
OCIDateTime |
ANSI DATE |
SQLT_DATE |
OCIDateTime |
TIMESTAMP |
SQLT_TIMESTAMP |
OCIDateTime |
TIMESTAMP WITH TIME ZONE |
SQLT_TIMESTAMP_TZ |
OCIDateTime |
TIMESTAMP WITH LOCAL TIME ZONE |
SQLT_TIMESTAMP_LTZ |
OCIInterval |
INTERVAL YEAR TO MONTH |
SQLT_INTERVAL_YM |
OCIInterval |
INTERVAL DAY TO SECOND |
SQLT_INTERVAL_DS |
日時と時間隔のデータを操作するOCI関数を次の表にリストします。これらの関数の詳細は、「OCIの日付関数、日時関数および時間隔関数」を参照してください。
通常、OCIDateTimeデータを操作する関数は、OCIDateデータにも有効です。
次の関数は、OCIDateTimeの値を操作します。この関数の中には、日時と時間隔の値に関する算術演算を行う関数もあります。また、特定の日時型に対してのみ動作する関数もあります。可能な型は、次のとおりです。
SQLT_DATE: DATE
SQLT_TIMESTAMP: TIMESTAMP
SQLT_TIMESTAMP_TZ: TIMESTAMP WITH TIME ZONE
SQLT_TIMESTAMP_LTZ: TIMESTAMP WITH LOCAL TIME ZONE
特定の関数に有効な入力型の詳細は、各関数の説明を参照してください。
表12-3 日時関数
| 関数 | 用途 |
|---|---|
|
|
日時の割当てを実行します。 |
|
|
指定の日付が有効かどうかをチェックします。 |
|
|
2つの日時値を比較します。 |
|
|
日時記述子を作成します。 |
|
|
ある日時型を別の日時型に変換します。 |
|
|
サイズ |
|
|
指定された書式に従って、指定の文字列を |
|
|
日時値の日付部分(年、月、日)を取得します。 |
|
|
日時の値からタイム(時間、分、秒、小数秒)を取得します。 |
|
|
日時値のタイム・ゾーン名部分を取得します。 |
|
OCIDateTimeGetTimeZoneOffset() |
日時値のタイム・ゾーン(時間、分)部分を取得します。 |
|
|
日時に時間隔を加算して、結果の日時を生成します。 |
|
|
日時から時間隔を減算して、その結果を日時に格納します。 |
|
|
2つの日時を入力値にして、その差異を時間隔に格納します。 |
|
|
現行のシステム日付および時刻を、タイム・ゾーン付きタイムスタンプとして取得します。 |
|
|
OCIDateTime記述子を配列に変換します。 |
|
|
指定された日付を指定の書式の文字列に変換します。 |
|
|
あるタイム・ゾーンの日付を別のゾーンの日付に変換します。 |
次のコードの断片によって、TIMESTAMP WITH LOCAL TIME ZONE列からデータを選択するOCIDateTimeデータ型の使用方法を示します。
...
/* allocate the program variable for storing the data */
OCIDateTime *tstmpltz = (OCIDateTime *)NULL;
/* Col1 is a timestamp with local time zone column */
OraText *sqlstmt = (OraText *)"SELECT col1 FROM foo";
/* Allocate the descriptor (storage) for the datatype */
status = OCIDescriptorAlloc(envhp,(void **)&tstmpltz, OCI_DTYPE_TIMESTAMP_LTZ,
0, (void **)0);
....
status = OCIStmtPrepare (stmthp, errhp, sqlstmt, (ub4)strlen ((char *)sqlstmt),
(ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT);
/* specify the define buffer for col1 */
status = OCIDefineByPos(stmthp, &defnp, errhp, 1, &tstmpltz, sizeof(tstmpltz),
SQLT_TIMESTAMP_LTZ, 0, 0, 0, OCI_DEFAULT);
/* Execute and Fetch */
OCIStmtExecute(svchp, stmthp, errhp, 1, 0,(OCISnapshot *) NULL,
(OCISnapshot *)NULL, OCI_DEFAULT)
At this point tstmpltz contains a valid timestamp with local time zone data. You
can get the time zone name of the datetime data using:
status = OCIDateTimeGetTimeZoneName(envhp, errhp, tstmpltz, (ub1 *)buf,
(ub4 *)&buflen);
...
次の関数は、時間隔データのみを操作します。対象となる時間隔の型の指定が必要な場合があります。可能な型は、次のとおりです。
SQLT_INTERVAL_YM: 年から月までの時間隔
SQLT_INTERVAL_DS: 日から秒までの時間隔
詳細は、各関数の説明を参照してください。
表12-4 時間隔関数
| 関数 | 用途 |
|---|---|
|
OCIIterCreate()(UNKNOWN STEP NUMBER) |
2つの時間隔を加算して、その結果の時間隔を生成します。 |
|
OCIIntervalAssign()(UNKNOWN STEP NUMBER) |
ある時間隔を別の時間隔にコピーします。 |
|
|
時間隔の妥当性をチェックします。 |
|
|
2つの時間隔を比較します。 |
|
|
時間隔をOracle数値で除算して、その結果の時間隔を生成します。 |
|
|
Oracle数値を時間隔に変換します。 |
|
|
時間隔文字列が指定されている場合、その文字列で表現される時間隔を生成します。 |
|
|
時間隔から日と秒の値を取得します。 |
|
|
時間隔から年と月を取得します。 |
|
|
時間隔をOracle数値で乗算して、その結果の時間隔を生成します。 |
|
|
日と秒を時間隔に設定します。 |
|
|
時間隔に年と月を設定します。 |
|
|
2つの時間隔を減算し、その結果を時間隔に格納します。 |
|
|
時間隔をOracle数値に変換します。 |
|
|
時間隔が指定されている場合、その時間隔を表現する文字列を生成します。 |
OCINumberデータ型は、Oracleの数値データ型(NUMBER、FLOAT、DECIMALなど)の表現に使用する不透明な構造体です。この型は、バインド・コールまたは定義コールで外部型コードSQLT_VNUを使用して、バインドまたは定義できます。
ファンクション内の数値という用語は、指定がないかぎり、OCINumber型の値を表します。
次の2つのコードの断片は、OCINumber型の属性の処理方法を示しています。2つ目の断片は、OCIDescribeAny()コールから戻されたOCINumber形式の値を符号なし整数に変換する方法を示します。
/* Example 1 */
struct person
{
OCINumber sal;
};
typedef struct person person;
OCIError *err;
person* steve;
person* scott;
person* jason;
OCINumber *stevesal;
OCINumber *scottsal;
OCINumber *debsal;
sword status;
int inum;
double dnum;
OCINumber ornum;
text buffer[21];
ub4 buflen;
sword result;
/* For this example, assume OCIEnv and OCIError are initialized. */
/* For this example, assume that steve, scott and jason are pointing to
person objects which have been pinned in the object cache. */
stevesal = &steve->sal;
scottsal = &scott->sal;
debsal = &jason->sal;
/* initialize steve's salary to be $12,000 */
inum = 12000;
status = OCINumberFromInt(err, &inum, sizeof(inum), OCI_NUMBER_SIGNED,
stevesal);
if (status != OCI_SUCCESS) /* handle error from OCINumberFromInt */;
/* initialize scott's salary to be same as steve's */
OCINumberAssign(err, stevesal, scottsal);
/* initialize jason's salary to be 20% more than steve's */
dnum = 1.2;
status = OCINumberFromReal(err, &dnum, sizeof(dnum), &ornum);
if (status != OCI_SUCCESS) /* handle error from OCINumberFromReal */;
status = OCINumberMul(err, stevesal, &ornum, debsal);
if (status != OCI_SUCCESS) /* handle error from OCINumberMul */;
/* give scott a 50% raise */
dnum = 1.5;
status = OCINumberFromReal(err, &dnum, sizeof(dnum), &ornum);
if (status != OCI_SUCCESS) /* handle error from OCINumberFromReal */;
status = OCINumberMul(err, scottsal, &ornum, scottsal);
if (status != OCI_SUCCESS) /* handle error from OCINumberMul */;
/* double steve's salary */
status = OCINumberAdd(err, stevesal, stevesal, stevesal);
if (status != OCI_SUCCESS) /* handle error from OCINumberAdd */;
/* get steve's salary in integer */
status = OCINumberToInt(err, stevesal, sizeof(inum), OCI_NUMBER_SIGNED, &inum);
if (status != OCI_SUCCESS) /* handle error from OCINumberToInt */;
/* inum is set to 24000 */
/* get jason's salary in double */
status = OCINumberToReal(err, debsal, sizeof(dnum), &dnum);
if (status != OCI_SUCCESS) /* handle error from OCINumberToReal */;
/* dnum is set to 14400 */
/* print scott's salary as DEM0001'8000.00 */
buflen = sizeof(buffer);
status = OCINumberToText(err, scottsal, (text *)"C0999G9999D99", 13,
(text *)"NLS_NUMERIC_CHARACTERS='.'' NLS_ISO_CURRENCY='Germany'",
54, &buflen, (text *)buffer);
if (status != OCI_SUCCESS) /* handle error from OCINumberToText */;
printf("scott's salary = %s\n", buffer);
/* compare steve and scott's salaries */
status = OCINumberCmp(err, stevesal, scottsal, &result);
if (status != OCI_SUCCESS) /* handle error from OCINumberCmp */;
/* result is positive */
/* read jason's new salary from string */
status = OCINumberFromText(err, (text *)"48'000.00", 9, (text
*)"99G999D99", 9,
(text *)"NLS_NUMERIC_CHARACTERS='.''", 27, debsal);
if (status != OCI_SUCCESS) /* handle error from OCINumberFromText */;
/* jason's salary is now 48000.00 */
次の例は、OCINumber形式でOCIDescribeAny()コールから戻された数値型(OCI_ATTR_MAXやOCI_ATTR_MINなど)を符号なしC整数に変換する方法を示します。
/* Example 2 */
ub4 max_seq_val = 0;
ub1 *max_valp = NULL;
ub4 max_val_size;
OCINumber max_val;
OCINumberSetZero(_errhp, &max_val);
OCIParam* parmdp = 0;
status = OCIAttrGet ((void *)_dschp, (ub4)OCI_HTYPE_DESCRIBE, &parmdp, 0,
(ub4)OCI_ATTR_PARAM, _errhp);
if (isError (status, _errhp))
{
return 0;
}
status = OCIAttrGet ((void *)parmdp, (ub4)OCI_DTYPE_PARAM, &max_valp,
&max_val_size, (ub4)OCI_ATTR_MAX, _errhp);
//create an OCINumber object from the ORACLE NUMBER FORMAT
max_val.OCINumberPart[0] = max_val_size; //set the length byte
memcpy(&max_val.OCINumberPart[1], max_valp, max_val_size); //copy the actual bytes
//now convert max_val to an unsigned C integer, max_seq_val
status = OCINumberToInt(_errhp, &max_val, sizeof(max_seq_val),
OCI_NUMBER_UNSIGNED, &max_seq_val);
固定長または可変長の文字列データは、Cプログラムに対してOCIString *として表現されます。
文字列の長さには、ヌル文字は含まれていません。
OCIString *型の変数のバインドと定義には、外部型コードSQLT_VSTを使用します。
Cのプログラマは、次の関数を使用して文字列のインスタンスを操作できます。
この例では、テキスト文字列を文字列に代入し、文字列の文字列部分へのポインタを文字列サイズとともに取得して、出力します。
OCIStringAssignText()で、vstring1パラメータを渡すために二重間接演算が使用されていることに注意してください。
OCIEnv *envhp;
OCIError *errhp;
OCIString *vstring1 = (OCIString *)0;
OCIString *vstring2 = (OCIString *)0;
text c_string[20];
text *text_ptr;
sword status;
strcpy((char *)c_string, "hello world");
/* Assign a text string to an OCIString */
status = OCIStringAssignText(envhp, errhp, c_string,
(ub4)strlen((char *)c_string),&vstring1);
/* Memory for vstring1 is allocated as part of string assignment */
status = OCIStringAssignText(envhp, errhp, (text *)"hello again",
(ub4)strlen("This is a longer string."),&vstring1);
/* vstring1 is automatically resized to store the longer string */
/* Get a pointer to the string part of vstring1 */
text_ptr = OCIStringPtr(envhp, vstring1);
/* text_ptr now points to "hello world" */
printf("%s\n", text_ptr);
可変長ロー・データは、OCIRaw *データ型を使用してCで表現されます。
OCIRaw *型の変数のバインドと定義には、外部型コードSQLT_LVBを使用します。
OCIRawの操作を行う関数は、次のとおりです。
この例では、ロー・データのブロックを設定し、そのデータへのポインタを取得します。
OCIRawAssignBytes()のコールで二重間接演算を使用していることに注意してください。
OCIEnv *envhp; OCIError *errhp; sword status; ub1 data_block[10000]; ub4 data_block_len = 10000; OCIRaw *raw1 = (OCIRaw *) 0; ub1 *raw1_pointer; /* Set up the RAW */ /* assume 'data_block' has been initialized */ status = OCIRawAssignBytes(envhp, errhp, data_block, data_block_len, &raw1); /* Get a pointer to the data part of the RAW */ raw1_pointer = OCIRawPtr(envhp, raw1);
Oracleには、可変長配列(VARRAY)とネストした表の2種類のコレクションがあります。Cアプリケーションでは、VARRAYはOCIArray *と表現され、ネストした表は、OCITable *と表現されます。これらの両方のデータ型は(後述するOCICollとOCIIterと同様に)、不透明な構造体です。
様々な汎用コレクション関数により、コレクション・データを操作できます。汎用コレクション関数は、VARRAYとネストした表の両方に使用できます。また、ネストした表固有の一連の関数もあります。
VARRAYまたはネストした表のインスタンスは、OCIObjectNew()を使用して割り当て、OCIObjectFree()を使用して解放できます。
Oracleには、可変長配列(VARRAY)とネストした表の2種類のコレクションがあります。VARRAYとネストした表は、どちらも汎用コレクション型のサブタイプとみなすことができます。
Cでは、汎用コレクションをOCIColl *、VARRAYをOCIArray *、ネストした表をOCITable *と表現します。Oracleには、汎用コレクションを操作するための一連の関数群(OCIColl *など)があります。これらの関数は、OCICollGetElem()のように接頭辞OCICollで始まります。OCIColl*()関数をコールして、VARRAYとネストした表を操作することもできます。
汎用コレクション関数は、2つの主なカテゴリにグループ化できます。
VARRAYまたはネストした表のデータを操作するグループ
コレクション・イテレータを使用してコレクションをスキャンするグループ
汎用コレクション関数は、VARRAYを操作する関数の完全な集合です。その他の関数は、特にネストした表で操作するための関数です。これらの関数は、OCITableExists()のように接頭辞OCITableで識別できます。
|
注意: コレクション関数に渡された索引は、0(ゼロ)が基数です。 |
次の汎用関数は、コレクション・データを操作します。
表12-7 コレクション関数
| 関数 | 用途 |
|---|---|
OCICollAppend() |
要素を追加します。 |
OCICollAssign() |
1つのコレクションを別のコレクションに割り当てます。 |
OCICollAssignElem() |
指定の索引位置に要素を割り当てます。 |
OCICollGetElem() |
指定の索引位置にある要素へのポインタを取得します。 |
OCICollGetElemArray() |
コレクションから要素の配列を取得します。 |
OCICollIsLocator() |
コレクションがロケータベースであるかどうかを示します。 |
OCICollMax() |
コレクションの上限を取得します。 |
OCICollSize() |
コレクションのカレント・サイズを取得します。 |
OCICollTrim() |
コレクションの終わりからn個の要素を切り捨てます。 |
次の汎用関数により、コレクション・イテレータを使用してコレクションをスキャンできます。イテレータは、OCIIter型であり、最初にOCIIterCreate()をコールして作成します。
この例では、コレクション・イテレータを作成し、それを使用してVARRAY(可変長配列)をスキャンします。
OCIEnv *envhp;
OCIError *errhp;
text *text_ptr;
sword status;
OCIArray *clients;
OCIString *client_elem;
OCIIter *iterator;
boolean eoc;
void *elem;
OCIInd *elemind;
/* Assume envhp, errhp have been initialized */
/* Assume clients points to a varray */
/* Print the elements of clients */
/* To do this, create an iterator to scan the varray */
status = OCIIterCreate(envhp, errhp, clients, &iterator);
/* Get the first element of the clients varray */
printf("Clients' list:\n");
status = OCIIterNext(envhp, errhp, iterator, &elem,
(void **) &elemind, &eoc);
while (!eoc && (status == OCI_SUCCESS))
{
client_elem = *((OCIString **)elem);
/* client_elem points to the string */
/*
the element pointer type returned by OCIIterNext() through 'elem' is
the same as that of OCICollGetElem(). Refer to OCICollGetElem() for
details. */
/*
client_elem points to an OCIString descriptor, so to print it out,
get a pointer to where the text begins
*/
text_ptr = OCIStringPtr(envhp, client_elem);
/*
text_ptr now points to the text part of the client OCIString, which
is a
NULL-terminated string
*/
printf(" %s\n", text_ptr);
status = OCIIterNext(envhp, errhp, iterator, &elem,
(void **)&elemind, &eoc);
}
if (status != OCI_SUCCESS)
{
/* handle error */
}
/* destroy the iterator */
status = OCIIterDelete(envhp, errhp, &iterator);
表はネストできます。つまり、変数、属性、パラメータまたは列として別の表の中に含めることができます。ネストした表には、OCITableDelete()関数で削除された要素がある場合があります。
たとえば、10個の要素を指定して作成した表があり、0から4と9の索引を持つ要素をOCITableDelete()で削除したとします。既存の最初の要素は要素5になり、既存の最後の要素は要素8になります。
前述のように、汎用コレクション関数を使用しても、ネストした表へのマッピングやネストした表の操作ができます。さらに、次に示すネストした表専用の関数もあります。ネストした表専用の関数は、VARRAYでは使用できません。
表12-9 ネストした表の関数
| 関数 | 用途 |
|---|---|
OCITableDelete() |
指定の索引位置にある要素を削除します。 |
OCITableExists() |
指定の索引位置に要素が存在するかどうかをテストします。 |
OCITableFirst() |
表の最初の既存要素の索引を戻します。 |
OCITableLast() |
表の最後の既存要素の索引を戻します。 |
OCITableNext() |
表の次の既存要素の索引を戻します。 |
OCITablePrev() |
表の前の既存要素の索引を戻します。 |
OCITableSize() |
削除した要素を除いた表のサイズを戻します。 |
ネストした表がオブジェクト・キャッシュにフェッチされた場合、その要素には、0(ゼロ)から始まり要素数から1を引いた数で終わる順序が一時的に付けられます。たとえば、40個の要素を持つ表では、0から39まで順序付けが行われます。
これらの位置序数を使用して、要素の値のフェッチと代入が行われます(たとえば、要素iへのフェッチや、要素jへの代入など。iとjは、指定の表の有効な位置序数です)。
表をデータベースにコピーすると、一時的な順序付けは失われます。表の要素に対して削除の操作を行うことがあります。操作を削除すると、一時的な空きが作成されます。つまり残りの表要素の位置序数は変更されません。
ネストした表のロケータを取り出すことができます。ロケータはコレクション値へのハンドルのようなもので、検索時に存在するデータベース・スナップショットについての情報を含みます。このスナップショット情報は、後にロケータを使用してコレクション要素がフェッチされるときに、データベースでコレクション値の正しいインスタンス化を取り出すために役立ちます。
LOBロケータと異なり、コレクション・ロケータでは、コレクション・インスタンスを変更できません。コレクション・ロケータは、単に正しいデータの検索のみを行います。このロケータを使用すると、アプリケーションは、大きなコレクション全体を取り出すことなく、ネストした表にハンドルを戻すことができます。
コレクション列または属性をフェッチするときにロケータが戻される必要がある場合、ユーザーは、RETURN AS LOCATOR指定を使用して、表がいつ作成されるかを指定します。
コレクションがロケータかどうかを判断するには、OCICollIsLocator()関数を使用します。
コレクション要素自体が直接的または間接的に別のコレクション型である場合があります。マルチレベル・コレクション型とは、このようなトップレベルのコレクション型に付けられた名前です。
マルチレベル・コレクションには、次のような特性があります。
他のコレクション型の集まりである場合があります。
コレクション属性を持つオブジェクトの集まりである場合があります。
ネスト・レベルの数に制限がありません。
VARRAYとネストした表の組合せを含めることができます。
表の列として使用できます。
OCIルーチンは、マルチレベル・コレクションを処理します。次のルーチンは、パラメータ*elemのOCIColl*を戻すことができるため、このパラメータは任意のコレクションのルーチンで使用できます。
OCICollgetElem()
OCIIterGetCurrent()
OCIIterNext()
OCIIterPrev()
次の関数は、コレクション要素を取得し、それを既存のコレクションに追加します。要素型が別のコレクションの場合、パラメータelemは、OCIColl*となることがあります。
OCICollAssignElem()
OCICollAppend()
たとえば、次の型と表を使用するとします。
type_1 (a NUMBER, b NUMBER) NT1 TABLE OF type_1 NT2 TABLE OF NT1
次のコードの断片がマルチレベル・コレクション全体で反復されます。
...
OCIColl *outer_coll;
OCIColl *inner_coll;
OCIIter *itr1, *itr2;
Type_1 *type_1_instance;
..
/* assume outer_coll points to a valid coll of type NT2 */
checkerr(errhp, OCIIterCreate(envhp, errhp, outer_coll, &itr1));
for(eoc = FALSE;!OCIIterNext(envhp, errhp, itr1, (void **) &elem,
(void **) &elem_null, &eoc) && !eoc;)
{
inner_coll = (OCIColl *)elem;
/* iterate over inner collection.. */
checkerr(errhp, OCIIterCreate(envhp, errhp, inner_coll, &itr2));
for(eoc2 = FALSE;!OCIIterNext(envhp, errhp, itr2, (void **)&elem2,
(void **) &elem2_null, &eoc2) && !eoc2;)
{
type_1_instance = (Type_1 *)elem2;
/* use the fields of type_1_instance */
}
/* close iterator over inner collection */
checkerr(errhp, OCIIterDelete(envhp, errhp, &itr2));
}
/* close iterator over outer collection */
checkerr(errhp, OCIIterDelete(envhp, errhp, &itr1));
...
REF(参照)とは、オブジェクトを識別する識別子です。これは、一意にオブジェクトを検索する不透明な構造体です。REFによって、オブジェクトは別のオブジェクトを指し示すことができます。
Cアプリケーションでは、OCIRef *でREFを表現します。
REF操作を行う関数は、次のとおりです。
この例では、2つのREFがNULLかどうかをテストし、両者が等しいかどうかを比較し、一方のREFをもう一方のREFに代入します。OCIRefAssign()のコールで二重間接演算を使用していることに注意してください。
OCIEnv *envhp;
OCIError *errhp;
sword status;
boolean refs_equal;
OCIRef *ref1, *ref2;
/* assume refs have been initialized to point to valid objects */
/*Compare two REFs for equality */
refs_equal = OCIRefIsEqual(envhp, ref1, ref2);
printf("After first OCIRefIsEqual:\n");
if(refs_equal)
printf("REFs equal\n");
else
printf("REFs not equal\n");
/*Assign ref1 to ref2 */
status = OCIRefAssign (envhp, errhp, ref1, &ref2);
if(status != OCI_SUCCESS)
/*error handling*/
/*Compare the two REFs again for equality */
refs_equal = OCIRefIsEqual(envhp, ref1, ref2);
printf("After second OCIRefIsEqual:\n");
if(refs_equal)
printf("REFs equal\n");
else
printf("REFs not equal\n");
この項では、OCIデータ型と型記述子について説明します。
ある型をCREATE TYPE文で作成すると、その型はサーバーに格納され、型記述子オブジェクト(TDO)に関連付けられます。さらに、型の各データ属性、型の各メソッド、各メソッドの各パラメータ、およびメソッドが戻す結果について、記述子オブジェクトがデータベースに格納されます。記述子オブジェクトの種類別に、関連付けられているOCIデータ型を次の表に示します。
表12-11 記述子オブジェクト
| 情報の種類 | OCIデータ型 |
|---|---|
|
型 |
OCIType |
|
型属性、コレクション要素、メソッド・パラメータ、メソッドの結果 |
OCITypeElem |
|
メソッド |
OCITypeMethod |
OCI関数の中には(OCIBindObject()およびOCIObjectNew()を含む)、TDOを入力パラメータとして必要とする関数があります。アプリケーションで、その型のTDOをOCIType変数で取得できるOCITypeByName()をコールしてTDOを取得できます。TDOを取得すると、必要に応じて、そのTDOをその他のコールに渡すことができます。
これらの機能によって、自己記述データをモデル化できます。異機種間データ型を同じ列に格納し、アプリケーションでそのデータの型を問い合せることができます。
後続の項の説明では、これらの定義が使用されています。
永続型: 永続型は、SQLのCREATE TYPE文を使用して作成します。データベースに永続的に格納されます。
一時型: データベースに永続的に格納されない匿名の型記述。プログラムによって一時的に作成されます。一時型は、型情報をアプリケーションの様々なコンポーネント間で、必要に応じて動的に交換するのに便利です。
自己記述データ: 型情報をその実際の内容とともにカプセル化したデータ。OCIでは、OCIAnyDataデータ型によって、このようなデータがモデル化されます。大部分のSQL型のデータ値は、OCIAnyDataに変換できます。また、元のデータ値に変換しなおすこともできます。SQLやPL/SQLでは、SYS.ANYDATA型によって、このデータセットがモデル化されます。
自己記述データセット: データ・インスタンスの集合(すべて同じ型)およびその型記述のカプセル化。この集合には、すべて同じ型記述が含まれている必要があります。OCIでは、OCIDataAnySetデータ型によって、このデータがモデル化されます。SQLやPL/SQLでは、SYS.ANYDATASET型によって、このデータセットがモデル化されます。
これらの型記述および自己記述データの作成と操作に必要なインタフェースは、OCI(C言語)およびSQLとPL/SQLの両方で使用可能です。次の各項では、関連するOCIインタフェースを説明します。
型インタフェースを使用すると、名前付きおよび匿名の一時オブジェクト型(属性付きで構造化されたもの)とコレクション型を作成できます。OCITypeBeginCreate()コールを使用して、一時オブジェクト型とコレクション型の作成を開始します(作成する型は、型コード・パラメータによって決定されます)。
OCIDescriptorAlloc()を使用して、パラメータ・ハンドルを割り当てる必要があります。次に、OCIAttrSet()を使用して、型情報(オブジェクト型の属性とコレクション要素の型に関する情報)を設定する必要があります。オブジェクト型の場合は、OCITypeAddAttr()を使用して属性情報をその型に追加します。最新の属性情報が追加された後で、OCITypeEndCreate()をコールする必要があります。
たとえば、次のようにします。
OCITypeBeginCreate( ...) /* Begin Type Creation */ OCIDescriptorAlloc(...) OCIAttrSet(...) OCITypeAddAttr(...) /* Add attribute 1 */ OCIAttrSet(...) OCITypeAddAttr(...) /* Add attribute 2 */ ... OCITypeEndCreate(...) /* End Type Creation */
コレクション型の場合は、コレクション要素型に関する情報を、OCITypeSetCollection()を使用して設定する必要があります。次に、OCITypeEndCreate()をコールして構成を終了します。
たとえば、次のようにします。
OCITypeBeginCreate( ...) /* Begin Type Creation */ OCIDescriptorAlloc(...) OCIAttrSet(...) OCITypeSetCollection(...) /* Set information on collection element */ OCITypeEndCreate(...) /* End Type Creation */
OCIDescribeAny()コールを使用すると、永続型に対応するOCITypeを取得できます。
OCIDescriptorAlloc()コールを使用すると、OCIParam(親ハンドルが環境ハンドルの場合)を割り当てることができます。その後、次の使用可能な属性の型を使用して、OCIAttrSet()をコールし、関連する型情報を設定できます。
OCI_ATTR_PRECISION
数値精度を設定します。(ub1 *)属性値を、精度値を保持しているバッファに渡します。
OCI_ATTR_SCALE
数値のスケールを設定します。(sb1 *)属性値を、スケール値を保持しているバッファに渡します。
OCI_ATTR_CHARSET_ID
キャラクタ・タイプのキャラクタ・セットIDを設定します。(ub2 *)属性値を、キャラクタ・セットIDを保持しているバッファに渡します。
OCI_ATTR_CHARSET_FORM
キャラクタ・タイプのキャラクタ・セット・フォームを設定します。(ub1 *)属性値を、キャラクタ・セット・フォーム値を保持しているバッファに渡します。
OCI_ATTR_DATA_SIZE
VARCHAR2、RAWなどの長さ。(ub2 *)属性値を、長さを保持しているバッファに渡します。
OCI_ATTR_TYPECODE
型コードを設定します。(ub2 *)属性値を、型コードを保持しているバッファに渡します。この属性は、最初に設定する必要があります。
OCI_ATTR_TDO
オブジェクトまたはコレクション属性のOCITypeを設定します。(OCIType *)属性値を、その属性に対応するOCITypeに渡します。AnyTypeの構成中に、このOCIParamを使用する場合、OCITypeが確保されていることを確認してください。一時型属性の場合の割当て時間は、少なくともトップレベルのOCITypeが作成されている時間内である必要があります。そうでない場合は、例外が戻されます。
組込み型の場合、SQL型属性にアクセス可能な型コード(OCI_ATTR_TYPECODEに対する許容値)は、次のとおりです。
OCI_TYPECODE_DATE、OCI_TYPECODE_NUMBER、
OCI_TYPECODE_VARCHAR、OCI_TYPECODE_RAW、
OCI_TYPECODE_CHAR、OCI_TYPECODE_VARCHAR2、
OCI_TYPECODE_VARCHAR、OCI_TYPECODE_BLOB、
OCI_TYPECODE_BFILE、OCI_TYPECODE_CLOB
OCI_TYPECODE_TIMESTAMP、OCI_TYPECODE_TIMESTAMP_TZ、
OCI_TYPECODE_TIMESTAMP_LTZ。
OCI_TYPECODE_INTERVAL_YM、OCI_TYPECODE_INTERVAL_DS。
属性/コレクション要素の型自体が別の一時型である場合は、OCI_ATTR_TYPECODEを次のいずれかに設定します。
OCI_TYPECODE_OBJECT、OCI_TYPECODE_REF(REFS用)、
OCI_TYPECODE_VARRAYまたはOCI_TYPECODE_TABLE。
次に、OCI_ATTR_TDOをその一時型に対応するOCITypeに設定します。
ユーザー定義の型属性の場合、OCI_ATTR_TYPECODEに対する許容値は、次のとおりです。
OCI_TYPECODE_OBJECT(オブジェクト型用)
OCI_TYPECODE_REF(REF型用)
OCI_TYPECODE_VARRAYまたはOCI_TYPECODE_TABLE
(コレクション用)
ユーザー定義の場合は、OCI_ATTR_TDOを適切な
ユーザー定義型のOCITypeに設定する必要があります。
OCIDescribeAny()コールを使用すると、永続型に対応するOCITypeを取得できます。たとえば、次のようにします。
OCIDescribeAny(svchp, errhp. (void *)"HR.EMPLOYEES",
(ub4)strlen("HR.EMPLOYEES"),
(ub1)OCI_OTYPE_NAME, (ub1)OCI_DEFAULT, OCI_PTYPE_TYPE, dschp);
記述ハンドル(dschp)から、OCIAttrGet()コールを使用して、OCITypeを取得できます。
一時型の記述を使用してOCIDescribeAny()をコールし、その型の動的記述にアクセスできます。OCITypeのポインタはobjtypeがOCI_OTYPE_PTRに設定されている場合、OCIDescribeAny()に直接渡すことができます。これによって、属性情報を名前および位置で取得できます。
一時型が組込み型である(組込みの型コードで作成されている)場合、この一時型(OCI_PTYPE_TYPE型に相当)を記述するパラメータ・ハンドルでは、次の追加の属性がサポートされます。
OCI_ATTR_DATA_SIZE
OCI_ATTR_TYPECODE
OCI_ATTR_DATA_TYPE
OCI_ATTR_PRECISION
OCI_ATTR_SCALE
OCI_ATTR_CHARSET_ID
OCI_ATTR_CHARSET_FORM
OCI_ATTR_LFPRECISION
OCI_ATTR_FSPRECISION
これらの属性には、型属性の記述時に通常付与される内容が含まれています。
|
注意: これらの属性は、一時的な組込み型に対してのみサポートされます。属性 OCI_ATTR_IS_TRANSIENT_TYPEとOCI_ATTR_IS_PREDEFINED_TYPEは、これらの型に該当します。永続型の場合、これらの属性は、その型の属性(OCI_PTYPE_TYPE_ATTR型に相当)のパラメータ・ハンドルでのみサポートされます。 |
OCIAnyDataは、型情報とその型のデータ・インスタンス(つまり、自己記述データ)をカプセル化します。OCIAnyDataは、OCIAnyDataConvert()コールを使用して、組込み型またはユーザー定義型のインスタンスから作成できます。このコールがOCIAnyDataへの変換(キャスト)を実行します。
オブジェクト型とコレクション型は、個別に作成することもできます。つまり、一度に1つのオブジェクト型の属性または一度に1つのコレクション要素を作成できます。個別に作成する場合は、型情報(OCIType)を使用してOCIAnyDataBeginCreate()をコールする必要があります。次に、OCIAnyDataAttrSet()を使用してオブジェクト型をコールし、OCIAnyDataCollAddElem()を使用してコレクション型をコールします。構成処理を終了するには、OCIAnyDataEndCreate()をコールする必要があります。
その後で、アクセス・ルーチンを起動できます。OCIAnyDataを対応する型のインスタンスに変換(キャスト)するには、OCIAnyDataAccess()コールを使用します。
オブジェクト型またはコレクション型に基づいたOCIAnyDataも個別にアクセスできます。
パフォーマンス改善のために、特別なコレクション構成とアクセス・コールが用意されています。このコールを使用すると、メモリー内での不要なコレクションの作成やコレクション全体のコピーなどが回避できます。たとえば、次のようにします。
OCIAnyDataConvert(...) /* Cast a built-in or user-defined type instance
to an OCIAnyData in 1 call. */
OCIAnyDataBeginCreate(...) /* Begin AnyData Creation */
OCIAnyDataAttrSet(...) /* Attribute-wise construction for object types */
または
OCIAnyDataCollAddElem(...) /* Element-wise construction for collections */ OCIAnyDataEndCreate(...) /* End OCIAnyData Creation */
OCIAnyDataTypeCodeToSqlt()関数は、AnyData値のOCITypeCodeを、OCIAnyData APIが戻す値の表現に対応するSQLTコードに変換します。
次の型コードは、OCIAnyData関数でのみ使用されます。
OCIDescribeAny()などその他の関数のコールでは、これらの型コードは戻されず、キャラクタ・セット・フォームを使用してデータがNCHARかどうか(キャラクタ・セット・フォームがSQLCS_NCHARかどうか)を判断する必要があります。
OCIAnyDataTypeCodeToSqlt()は、OCI_TYPECODE_CHARおよびOCI_TYPECODE_VARCHAR2を、キャラクタ・セット・フォームがSQLCS_IMPLICITの出力値SQLT_VST(OCIStringマッピングに対応)に変換します。また、OCI_TYPECODE_NVARCHAR2は、キャラクタ・セット・フォームがSQLCS_NCHARのSQLT_VST(OCIStringマッピングはOCIAnyData APIで使用されます)を戻します。
OCIAnyDataSetは、型情報とその型のインスタンスの集合をカプセル化します。構成処理を開始するには、OCIAnyDataSetBeginCreate()をコールします。OCIAnyDataSetAddInstance()をコールして新規インスタンスを追加すると、このコールは、そのインスタンスに対応するOCIAnyDataを戻します。
次に、このインスタンスを作成するためのOCIAnyData関数を呼び出すことができます。すべてのインスタンスが追加された後に、OCIAnyDataSetEndCreate()をコールします。
アクセスする場合は、OCIAnyDataSetGetInstance()をコールして、そのインスタンスに対応するOCIAnyDataを取得します。順次アクセスのみがサポートされます。その後で、OCIAnyDataアクセス関数を起動できます。たとえば、次のようにします。
OCIAnyDataSetBeginCreate(...) /* Begin AnyDataSet Creation */
OCIAnyDataSetAddInstance(...) /* Add a new instance to the AnyDataSet */
/* Use the OCIAnyData*() functions to create
the instance */
OCIAnyDataSetEndCreate(...) /* End OCIAnyDataSet Creation */
この項では、オブジェクトやコレクションなどの名前付きデータ型とREFのバインドについて説明します。
名前付きデータ型(オブジェクト型やコレクション)のバインドでは、OCIBindByName()またはOCIBindByPos()の後にもう1つのバインド・コールが必要です。OCIBindObject()というOCIオブジェクト型バインド・コールで、オブジェクト型のバインド固有の追加属性を設定します。OCIアプリケーションでは、このコールを使用して、オブジェクト・データ型の列を持つ表からデータをフェッチします。
OCIBindObject()コールでパラメータの1つとして、名前付きデータ型のTDOを受け取ります。TDO(データ型OCIType)は、名前付きデータ型の作成時に作成され、データベースに格納されます。TDOには、型とその属性についての情報が入っています。アプリケーションでは、OCITypeByName()をコールしてTDOを取得できます。
OCIBindObject()コールでは、名前付きデータ型バインド用の標識変数または構造体も設定します。
名前付きデータ型をバインドするときは、SQLT_NTYデータ型定数を使用して、バインドするプログラム変数のデータ型を指定します。SQLT_NTYは、名前付きデータ型を表現するC構造体をバインドすることを示します。この構造体へのポインタをバインド・コールに渡します。
スーパータイプがある場合は、継承とインスタンスの代入性を使用して、サブタイプのインスタンスをバインドできます。
場合によっては、名前付きデータ型の操作に3つのバインド・コールが必要になることがあります。たとえば、名前付きデータ型の静的配列をPL/SQL表にバインドするには、OCIBindByName()、OCIBindArrayOfStruct()およびOCIBindObject()という3つのコールを呼び出してください。
|
関連項目:
|
名前付きデータ型と同様に、REFのバインドも2ステップで処理します。最初にOCIBindByName()またはOCIBindByPos()をコールし、次にOCIBindObject()をコールします。
REFは、SQLT_REFデータ型を使用してバインドします。SQLT_REFを使用する場合、バインドするプログラム変数は、OCIRef *型の変数にする必要があります。
スーパータイプへのREFがある場合は、継承とREFの代入性を使用して、REFの値をサブタイプのインスタンスにバインドできます。
|
関連項目:
|
この項では、名前付きデータ型とREFのバインドを処理するときに注意する必要がある重要な追加情報について説明します。メモリー割当てと標識変数の使用方法についての参照先も示します。
バインド対象のデータ型がSQLT_NTYである場合は、OCIBindObject()コールのインジケータ構造体パラメータ(void ** indpp)が使用され、スカラー・インジケータは完全に無視されます。
データ型がSQLT_REFの場合は、スカラー・インジケータが使用され、OCIBindObject()のインジケータ構造体のパラメータは完全に無視されます。
インジケータ構造体の使用はオプションです。ユーザーは、indppパラメータのNULLポインタをOCIBindObject()コールに渡すことができます。これは、バインド時に、オブジェクトがアトミックNULLではないこと、そのオブジェクトのどの属性もNULLではないことを意味します。
OCIBindObject()コールのインジケータ構造体サイズ・ポインタindspおよびプログラム変数サイズ・ポインタpgvspは、オプションです。これらのパラメータが不要な場合、ユーザーはNULLを渡すことができます。
この項では、名前付きデータ型(オブジェクト、コレクションなど)とREFの定義について説明します。
名前付きデータ型(オブジェクト型、NESTED TABLE、VARRAY)の定義には、2つの定義コールが必要です。最初にdtyパラメータにSQLT_NTYを指定して、OCIDefineByPos()をコールします。アプリケーションでは、OCIDefineByPos()に続いて、OCIDefineObject()をコールする必要があります。この場合、OCIDefineByPos()のデータ・バッファ・ポインタは無視され、名前付きデータ型定義に関係するその他の属性は、OCIオブジェクト属性定義コールであるOCIDefineObject()を使用して設定されます。
そのコールでは名前付きデータ型定義用のSQLT_NTYデータ型定数を指定します。この場合、結果のデータが名前付きデータ型のホスト言語表現の中にフェッチされます。通常、これはObject Type Translator(OTT)によって生成されるC構造体になります。
OCIDefineObject()コールを行うときは、C構造体のアドレスへのポインタ(事前割当て済またはそれ以外)を定義する必要があります。オブジェクトは、OCIObjectNew()を使用して作成され、キャッシュまたはユーザー定義のメモリーに割り当てられます。
ただし、継承が存在する場合は、オブジェクト・キャッシュ内のオブジェクトを使用し、スタックのユーザー・メモリーから割り当てられたオブジェクトを渡さないことをお薦めします。インスタンスの代入性により、クライアントにスーパータイプのインスタンスがあるときは、サーバーがサブタイプのインスタンスを戻す場合があるためです。サーバーは、オブジェクトを動的にサイズ変更する必要があります。この操作は、キャッシュ内のオブジェクトに対してのみ可能です。
名前付きデータ型と同様に、REF出力変数の定義も2ステップで処理します。第1ステップでOCIDefineByPos()をコールし、第2ステップでOCIDefineObject()をコールします。また、名前付きデータ型の場合と同様に、OCIDefineByPos()のdtyパラメータにSQLT_REFデータ型定数を渡します。
SQLT_REFでは、アプリケーションで結果として得るデータをOCIRef *型変数にフェッチすることを示します。第6章で説明するとおり、このREFをオブジェクトの確保とナビゲーションの一部として使用します。
この項には、名前付きデータ型とREFの定義を処理するときに注意する必要がある重要な追加情報について説明します。メモリー割当てと標識変数の使用方法についての参照先も示します。
PL/SQL OUTバインドとは、プレースホルダをPL/SQLブロックの出力変数にバインドすることです。SQL文では出力バッファを定義コールで設定しますが、PL/SQLブロックでは出力バッファをバインド・コールで設定します。詳細は、「PL/SQLのプレースホルダのバインド」を参照してください。
定義対象のデータ型がSQLT_NTYの場合は、OCIDefineObject()コールのインジケータ構造体パラメータ(void ** indpp)が使用され、スカラー・インジケータは完全に無視されます。
データ型がSQLT_REFの場合は、スカラー・インジケータが使用され、OCIDefineObject()のインジケータ構造体のパラメータは完全に無視されます。
インジケータ構造体の使用はオプションです。ユーザーは、indppパラメータのNULLポインタをOCIDefineObject()コールに渡すことができます。これは、フェッチまたはPL/SQL OUTバインド時に、NULLであるかどうかの情報をユーザーが認識しないことを意味します。
SQL定義またはPL/SQL OUTバインドの場合、プログラマは出力変数またはインジケータのいずれかに対して事前割当済メモリーを渡すことができます。渡されると、その事前割当て済メモリーが結果データの格納に使用され、2次メモリー(アウト・ライン・メモリー)がある場合は、その内容がすべて割当て解除されます。事前割当て済メモリーは、キャッシュ(OCIObjectNew()コールの結果)から取得する必要があります。
|
注意: キャッシュからではなく、クライアント・アプリケーションで所有するメモリー領域からメモリーを割り当てる場合は、オブジェクトにアウト・ライン2次メモリーがないことを確認する必要があります。 |
SQLT_NTY型を使用したオブジェクト定義の場合、オブジェクト・メモリーの事前割当てを行うクライアント・アプリケーションではOCIObjectNew()関数を使用する必要があります。クライアント・アプリケーションでは、malloc()を使用する割当てや、スタックへの割当てなど、独自のプライベート・メモリー領域へのオブジェクトの割当ては行わないでください。OCIObjectNew()関数によって、オブジェクト・キャッシュにオブジェクトが割り当てられます。割り当てられたオブジェクトは、OCIObjectFree()を使用すると解放できます。OCIObjectNew()およびOCIObjectFree()の詳細は、第18章「OCIのナビゲーショナル関数と型関数」を参照してください。
|
注意: ユーザーがオブジェクト・メモリーを事前割当てせずに、出力変数をNULLポインタ値に初期化した場合でも、 OCIDefineObject()の動作に変化はありません。この場合、オブジェクトはOCIライブラリによって暗黙的にオブジェクト・キャッシュ内に割り当てられます。 |
SQL定義またはPL/SQL OUTバインドの場合、ユーザーが出力変数またはインジケータに対してNULLアドレスを渡すと、その変数またはインジケータ用のメモリーは、OCIによって暗黙的に割り当てられます。
SQLT_NTY型の出力オブジェクトが(SQL定義またはPL/SQL OUTバインドで)アトミックNULLの場合は、NULLインジケータ構造体のみが(必要であれば暗黙的に)割り当てられてデータが挿入され、オブジェクトがアトミックNULLであることが明示されます。トップレベルのオブジェクト自体は、暗黙的に割り当てられません。
アプリケーションでは、OCIObjectFree()をコールしてインジケータを解放できます。トップレベルのオブジェクトがある場合は(アトミックNULLでないオブジェクトの場合など)、そのトップレベルのオブジェクトをOCIObjectFree()で解放すると、インジケータも解放されます。オブジェクトがアトミックNULLである場合は、トップレベルのオブジェクトがないので、インジケータを個別に解放する必要があります。
OCIDefineObject()コールのインジケータ構造体サイズ・ポインタindsp、およびプログラム変数サイズ・ポインタpgvspは、オプションです。これらのパラメータが不要な場合、ユーザーはNULLを渡すことができます。
これまでの章では、OCIのバインド操作と定義操作について説明しました。「OCIのプレースホルダのバインド」では、OCIバインド操作の基本について、「OCIでの出力変数の定義」では、OCI定義操作の基本について説明しています。名前付きデータ型およびREFのバインドと定義の詳細は、第5章「OCIでのバインドおよび定義」を参照してください。
バインドと定義の基本的な機能に関する章では、アプリケーションでSQL文の入力(バインド)値として、または問合せに対する出力(定義)バッファとしてスカラー変数またはスカラーの配列を使用する方法を示しています。
名前付きデータ型とREFに関する項では、オブジェクトや参照をバインドまたは定義する方法を説明しています。第11章「OCIオブジェクト・リレーショナル・プログラミング」では、さらにオブジェクト参照の確保、オブジェクト・ナビゲーションおよび埋込みインスタンスのフェッチについて説明しています。
この項では、この章で説明するデータ型マッピングに従って、個々の属性値のバインドと定義の方法を説明します。
この章で定義する型の1つであるOCINumber、OCIStringなどの変数は、一般にアプリケーションで宣言でき、適切なデータ型コードが指定されるかぎり、OCIバインドまたは定義操作で直接使用できます。次の表は、Cマッピングとともにバインドおよび定義で使用できるデータ型と、バインドまたは定義コールのdty(データ型コード)で指定する必要のある、OCI外部データ型のリストです。
表12-12 バインドおよび定義用のデータ型マッピング
| データ型 | Cマッピング | OCIの外部データ型とコード |
|---|---|---|
|
Oracle |
OCINumber |
VARNUM(SQLT_VNU) |
|
Oracle |
OCIDate |
SQLT_ODT |
|
|
OCILobLocator * |
SQLT_BLOB |
|
|
CILobLocator * |
SQLTY_LOB |
|
|
OCIString * |
SQLT_VST(表の後の注意を参照) |
|
|
OCIRaw * |
SQLT_LVB(表の後の注意を参照) |
|
|
OCIString * |
SQLT_VST |
|
オブジェクト |
struct * |
名前付きデータ型(SQLT_NTY) |
|
|
OCIRef * |
REF(SQLT_REF) |
|
|
OCIArray * |
名前付きデータ型(SQLT_NTY) |
|
NESTED TABLE |
OCITable * |
名前付きデータ型(SQLT_NTY) |
|
|
OCIDateTime * |
詳細は、「日時および時間隔(OCIDateTime、OCIInterval)」を参照してください。 |
|
|
OCIInterval * |
詳細は、「日時および時間隔(OCIDateTime、OCIInterval)」を参照してください。 |
|
注意: OCIString *型の定義変数にデータをフェッチするには、最初にOCIStringResize()ルーチンを使用して文字列のサイズを設定する必要があります。そのためには、記述操作によって選択リスト・データの長さを取得することが必要な場合があります。同様に、OCIRaw *も最初にOCIRawResize()でサイズを決定する必要があります。 |
次の項では、Cにマッピングしたデータ型のOCIアプリケーションでの使用例を示します。
この項では、OCIのバインド操作と定義操作でOCINumber型の変数を使用する例を示します。
この例では、次のようなpersonというオブジェクト型が作成されていると仮定します。
CREATE TYPE person AS OBJECT (name varchar2(30), salary number);
この型を使用して、person型の列を持つemployees(従業員)表を作成します。
CREATE TABLE employees (emp_id number, job_title varchar2(30), emp person);
Object Type Translator(OTT)では、personに対して次のC構造体とNULLインジケータ構造体が生成されます。
struct person
{ OCIString * name;
OCINumber salary;};
typedef struct person person;
struct person_ind
{ OCIInd _atomic;
OCIInd name;
OCIInd salary;}
typedef struct person_ind person_ind;
employees表は値が入り、OCIアプリケーションはpersonの変数を宣言するとします。
person *my_person;
また、次のように、SELECT文によってオブジェクトがその変数にフェッチされたとします。
text *mystmt = (text *) "SELECT person FROM employees
WHERE emp.name='Andrea'";
この場合は、適切な名前付きデータ型のOCI定義コールを使用して、my_personをこの文の出力変数として定義する必要があります。詳細は、「OCIでの拡張定義操作」で説明されています。この文を実行すると、Andreaという名のpersonオブジェクトが、my_person変数に取り出されます。
オブジェクトがmy_personに取り出されると、OCIアプリケーションでmy_personの名前や給与などの属性にアクセスできるようになります。
アプリケーションでは、別の従業員の給与を、次のようにAndreaと同様に更新します。
text *updstmt = (text *) "UPDATE employees SET emp.salary = :newsal
WHERE emp.name = 'MONGO'";
my_person->salaryに格納されているAndreaの給与は、プレースホルダ:newsalにバインドされ、VARNUMの外部データ型(データ型コード=6)をバインド操作に指定します。
OCIBindByName(...,":newsal",...,&my_person->salary,...,6,...); OCIStmtExecute(...,updstmt,...);
文を実行すると、データベースにあるMongoの給与が、my_personに格納されているように、Andreaの給与と等しくなります。
その反対に、Mongoの給与をデータベースに問い合せてから給与に必要な割当てを行うことにより、アプリケーションでAndreaの給与をMongoの給与と同額になるよう更新できます。
text *selstmt = (text *) "SELECT emp.salary FROM employees
WHERE emp.name = 'MONGO'";
OCINumber mongo_sal;
...
OCIDefineByPos(...,1,...,&mongo_sal,...,6,...);
OCIStmtExecute(...,selstmt,...);
OCINumberAssign(...,&mongo_sal, &my_person->salary);
この場合には、アプリケーションでOCINumberの型の出力変数を宣言し、それを定義のステップで使用します。この場合は、位置1の出力変数を定義し、適切なデータ型コード(VARNUMの場合は6)を使用します。
mongo_salというOCINumberに給与の値をフェッチし、OCI関数OCINumberAssign()を使用して、現在キャッシュ内にあるAndreaオブジェクトのコピーに新しい給与を割り当てます。データベース内にあるデータを変更するには、変更内容をサーバーにフラッシュする必要があります。
前の項では、Oracleデータ型がバインド操作と定義操作に与える柔軟性に関して、その要点をいくつか説明しました。この項では、同じ操作を異なる方法でどのように実行するかを説明します。OCIアプリケーションにおけるOracleの新しいデータ型の様々な使用方法を示すことが目的です。
この項の例は、特定のOCI作業を実行するのに使用されるコールのフローを説明します。ここでは拡張擬似コードを使用しています。実際の関数名を使用していますが、簡素化のためにパラメータとタイプキャストは一部のみ示します。ハンドルの割当てなど、その他の必要なOCIコールも省略しています。
これから示す例のシナリオは、次のとおりです。
ある病院のemployees表に、BRUCEという従業員が存在します。前の項のperson型およびemployees表の作成文を参照してください。
Bruceの現在の職種は、RADIOLOGISTです。
Bruceは、RADIOLOGY_CHIEFに昇進したため、それに伴った昇給があります。
次に示すように、病院の給与はドル単位の整数値とし、職種に応じて設定し、salariesという表に格納します。
CREATE TABLE salaries (job_title varchar2(20), salary integer));
Bruceの給与を昇進にあわせて更新します。
前述の作業を実行するには、アプリケーションでRADIOLOGY_CHIEFに該当する給与をsalaries表から取り出し、Bruceの給与を更新します。別のステップで、Bruceの新しい職種と変更済オブジェクトをデータベースに書き込みます。
次のようにperson型の変数を宣言したとします。
person * my_person;
そして、Bruceに対応するオブジェクトをこの変数にフェッチしたとします。この場合に給与更新を実行するには、次の各項に示す3通りの方法があります。
この例では次の方法をとります。
整変数を使用して従来のOCI定義を行い、データベースから新しい給与を取り出します。
整数をOCINumberに変換します。
新しい給与をBruceに割り当てます。
#define INT_TYPE 3 /* datatype code for sword integer define */
text *getsal = (text *) "SELECT salary FROM salaries
WHERE job_title='RADIOLOGY_CHIEF'";
sword new_sal;
OCINumber orl_new_sal;
...
OCIDefineByPos(...,1,...,new_sal,...,INT_TYPE,...);
/* define int output */
OCIStmtExecute(...,getsal,...);
/* get new salary as int */
OCINumberFromInt(...,new_sal,...,&orl_new_sal);
/* convert salary to OCINumber */
OCINumberAssign(...,&orl_new_sal, &my_person->salary);
/* assign new salary */
この方法では、方法1のステップを1つ省略します。
OCINumber型の出力変数を定義します。そのため値を取り出した後の変換は必要ありません。
新しい給与をBruceに割り当てます。
#define VARNUM_TYPE 6 /* datatype code for defining VARNUM */
text *getsal = (text *) "SELECT salary FROM salaries
WHERE job_title='RADIOLOGY_CHIEF'";
OCINumber orl_new_sal;
...
OCIDefineByPos(...,1,...,orl_new_sal,...,VARNUM_TYPE,...);
/* define OCINumber output */
OCIStmtExecute(...,getsal,...); /* get new salary as OCINumber */
OCINumberAssign(...,&orl_new_sal, &my_person->salary);
/* assign new salary */
この方法では、操作全体を1回の定義とフェッチで実行します。出力変数を介在させる必要はなく、データベースから取り出した値を、キャッシュに格納されているオブジェクトの給与属性に直接フェッチします。
Bruceはオブジェクト・キャッシュ内に確保されているため、その給与属性の位置を定義変数として使用し、その変数に直接実行し、フェッチを行います。
#define VARNUM_TYPE 6 /* datatype code for defining VARNUM */
text *getsal = (text *) "SELECT salary FROM salaries
WHERE job_title='RADIOLOGY_CHIEF'";
...
OCIDefineByPos(...,1,...,&my_person->salary,...,VARNUM_TYPE,...);
/* define bruce's salary in cache as output variable */
OCIStmtExecute(...,getsal,...);
/* execute and fetch directly */
この3つの例に示したように、Cデータ型を使用すると柔軟性のあるバインドと定義を実行できます。たとえば、整数をフェッチし、OCINumberに変換して操作できます。この方法では、OCINumberを中間的な変数として使用し、問合せの結果を格納できます。あるいは、目的のオブジェクトのOCINumber属性にデータを直接フェッチすることもできます。
|
注意: OCIのこれらすべての例では、問合せの実行前に出力変数が定義されている場合、結果として得られるデータは、出力バッファに直接プリフェッチされることを覚えておいてください。 |
これらの例で、変更がデータベースに確実に永続的に書き込まれるためには、追加ステップが必要です。この追加ステップには、SQLのUPDATEコールとOCIトランザクションのコミット・コールを含めることができます。
これらの例ではすべて定義操作を処理していますが、同じような状況がバインドにも当てはまります。
同様に、これらの例ではOCINumber型のみを扱いましたが、この章でこれから説明する他のOracleのC型についても、同じような種々の操作を実行できます。
次の部分的なコードは、OCIBindObject()やOCIDefineObject()などのSQLT_NTYバインド・コールおよびSQLT_NTY定義コールの使用方法を示しています。それぞれの例で、前に定義したSQL文を処理します。
/*
** This example performs a SQL insert statement
*/
void insert(envhp, svchp, stmthp, errhp, insstmt, nrows)
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIStmt *stmthp;
OCIError *errhp;
text *insstmt;
ub2 nrows;
{
OCIType *addr_tdo = (OCIType *)0 ;
address addrs;
null_address naddrs;
address *addr = &addrs;
null_address *naddr = &naddrs;
sword custno =300;
OCIBind *bnd1p, *bnd2p;
ub2 i;
/* define the application request */
checkerr(errhp, OCIStmtPrepare(stmthp, errhp, (text *) insstmt,
(ub4) strlen((char *)insstmt),
(ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT));
/* bind the input variable */
checkerr(errhp, OCIBindByName(stmthp, &bnd1p, errhp, (text *) ":custno",
(sb4) -1, (void *) &custno,
(sb4) sizeof(sword), SQLT_INT,
(void *) 0, (ub2 *)0, (ub2 *)0, (ub4) 0, (ub4 *) 0,
(ub4) OCI_DEFAULT));
checkerr(errhp, OCIBindByName(stmthp, &bnd2p, errhp, (text *) ":addr",
(sb4) -1, (void *) 0,
(sb4) 0, SQLT_NTY, (void *) 0, (ub2 *)0, (ub2 *)0,
(ub4) 0, (ub4 *) 0, (ub4) OCI_DEFAULT));
checkerr(errhp,
OCITypeByName(envhp, errhp, svchp, (const text *)
SCHEMA, (ub4) strlen((char *)SCHEMA),
(const text *)"ADDRESS_VALUE",
(ub4) strlen((char *)"ADDRESS_VALUE"),
(text *)0, 0, OCI_DURATION_SESSION,
OCI_TYPEGET_HEADER, &addr_tdo));
if(!addr_tdo)
{
printf("Null tdo returned\n");
return;
}
checkerr(errhp, OCIBindObject(bnd2p, errhp, addr_tdo, (void **) &addr,
(ub4 *) 0, (void **) &naddr, (ub4 *) 0));
/*
** This example executes a SELECT statement from a table which includes
** an object.
*/
void selectval(envhp, svchp, stmthp, errhp)
OCIEnv *envhp;
OCISvcCtx *svchp;
OCIStmt *stmthp;
OCIError *errhp;
{
OCIType *addr_tdo = (OCIType *)0;
OCIDefine *defn1p, *defn2p;
address *addr = (address *)NULL;
sword custno =0;
sb4 status;
/* define the application request */
checkerr(errhp, OCIStmtPrepare(stmthp, errhp, (text *) selvalstmt,
(ub4) strlen((char *)selvalstmt),
(ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT));
/* define the output variable */
checkerr(errhp, OCIDefineByPos(stmthp, &defn1p, errhp, (ub4) 1, (void *)
&custno, (sb4) sizeof(sword), SQLT_INT, (void *) 0, (ub2 *)0,
(ub2 *)0, (ub4) OCI_DEFAULT));
checkerr(errhp, OCIDefineByPos(stmthp, &defn2p, errhp, (ub4) 2, (void *)
0, (sb4) 0, SQLT_NTY, (void *) 0, (ub2 *)0,
(ub2 *)0, (ub4) OCI_DEFAULT));
checkerr(errhp,
OCITypeByName(envhp, errhp, svchp, (const text *)
SCHEMA, (ub4) strlen((char *)SCHEMA),
(const text *) "ADDRESS_VALUE",
(ub4) strlen((char *)"ADDRESS_VALUE"),
(text *)0, 0, OCI_DURATION_SESSION,
OCI_TYPEGET_HEADER, &addr_tdo));
if(!addr_tdo)
{
printf("NULL tdo returned\n");
return;
}
checkerr(errhp, OCIDefineObject(defn2p, errhp, addr_tdo, (void **)
&addr, (ub4 *) 0, (void **) 0, (ub4 *) 0));
checkerr(errhp, OCIStmtExecute(svchp, stmthp, errhp, (ub4) 1, (ub4) 0,
(OCISnapshot *) NULL, (OCISnapshot *) NULL, (ub4) OCI_DEFAULT));