23 OCIでのJSONのサポート

SQL型JSONのサポートは、JSONデータ用に設計されています。Oracle DatabaseではJSONデータにJSON型を使用することをお薦めします。

この章には次のトピックが含まれます:

23.1 JSONデータ型のサポート

Oracle Databaseリリース20c以降では、SQL型JSONのサポートは、特にJSONデータ用に設計されています。Oracle DatabaseではJSONデータにJSON型を使用することをお薦めします。これにはバイナリ形式OSONが使用されます。これは、Oracle DatabaseサーバーとOracle Databaseクライアントの両方で問合せおよび更新を迅速に行うための、Oracle用に最適化されたバイナリJSON形式です。JSONデータ型を使用するには、compatibleデータベース初期化パラメータに20以上を設定する必要があります。

JSONデータがSQLデータ型JSONの場合、Oracleでは標準のJSON言語のスカラー型(数値、文字列、ブール、nullなど)のセットが拡張され、SQLスカラー型に対応するスカラーが含まれています(binary、date、timestamp、year-month interval、day-second interval、double、floatなど)。これにより、JSON言語が拡張され、JSON言語とSQLの間でスカラー・データの変換がより簡単になり、データの損失がなくなります。

23.1.1 JSONのOCI表現

この項では、JSONのOCI表現について説明します。

標準JSONは、言語または表記法として、オブジェクト、配列、数値、文字列、ブール、nullなどのデータ型が事前定義されています。オブジェクトと配列を除くすべてのJSON言語型はスカラー型です。

OCIJson記述子は、OCIのJSONドキュメントを表すために使用されます。これは記述子タイプOCI_DTYPE_JSONで識別されます。この記述子は、OCIDescriptorAlloc()関数およびOCIDescriptorFree ()関数を使用して割当ておよび解放できます。ユーザーは、OCIArrayDescriptorAlloc()関数およびOCIArrayDescriptorFree()関数をそれぞれ使用して、記述子の配列の割当ておよび解放を行うことができます。

JSONドキュメント構造の例を次の図に示します。

図23-1 JSONドキュメントの例

図23-1の説明が続きます
「図23-1 JSONドキュメントの例」の説明

OCIJsonDomDocGet()関数を使用すると、JsonDomDoc *で表される基礎となるJSON DOM (Document Object Model)コンテナへの参照を取得できます。JSON DOMドキュメントには、オブジェクト型、配列型またはスカラー型のいずれかのノードが含まれています。

表23-1 型の作成および定数

プログラム変数 型定数
JsonDomScalar JZNDOM_SCALAR
JsonDomArray JZNDOM_ARRAY
JsonDomObject JZNDOM_OBJECT

注意:

JsonDomNodeは不完全な型です。前述の表に示されている型の値は、(JsonDomNode *)型に設定できます。

23.2 可変DOMと不変DOM

この項では、JSON DOMの可変形式および不変形式について説明します。

OCI記述子を使用すると、可変DOMまたは不変DOMの2つの形式のJSON DOMのいずれかを取得できます。可変DOM内のDOMノードは変更できます。それに対して、不変DOMは読取り専用であり、DOMに対して書込み操作を行うとエラーが発生します。記述子によって保持されるデフォルトのDOMは、OCIAttrSet()関数を使用してブール属性型に変更されないかぎり、不変です。

注意:

OCIJsonDomDocSet()関数を使用して、記述子にJSON DOMコンテナを設定できます。ソースDOMは、記述子の形式と異なる形式にすることができます。つまり、ソースを可変DOMにして、ターゲット記述子に不変DOMを設定できます。

23.2.1 可変DOMとしてのJSONのマニフェスト

OCIクライアント・アプリケーションでは、JSONのコンテンツを可変DOMとしてマニフェストできます。XDKのC DOM関数を使用すると、JSONドキュメントとの間でJSON要素(オブジェクト、配列、スカラーなど)の読取りおよび書込みを行うことができます。

次のコード・スニペットの例は、OCIJson*記述子との間で可変モードでDOMドキュメントの参照を設定する方法を示しています。

/* Set the JSON DOM MUTABLE Attribute to TRUE */
boolean attr = TRUE;
rc = OCIAttrSet(jsond,                  /* OCIJson descriptor */  
         OCI_DTYPE_JSON,                /* Descriptor type */ 
         &attr,                 
         OCI_ATTR_JSON_DOM_MUTABLE,     /* Attribute type */
         errhp);
if (rc != OCI_SUCCESS)  goto err_hndlr;  

OCI_ATTR_JSON_DOM_MUTABLE記述子属性にTRUEを設定するには、OCIAttrSet()を使用します。

OCIアプリケーションでは、次のタイプのJSON入力を記述子に設定することもできます。
  • バッファ入力 – oratext*
  • ストリーミング入力 – orastream *

23.2.2 不変DOMとしてのJSONのマニフェスト

OCIクライアント・アプリケーションは、バイナリJSONに基づく不変の線形バイナリ・バッファとしてJSONコンテンツをマニフェストできます。

次のコード・スニペットの例は、OCIJson*記述子との間で不変モードでDOMドキュメントの参照を設定する方法を示しています。この設定を記述子に対して行わない場合は、値FALSEにデフォルト設定されます。

/* Set the JSON DOM IMMUTABLE Attribute to FALSE */ 
boolean attr = FALSE; 
rc = OCIAttrSet(jsond,                        /* OCIJson descriptor */
                OCI_DTYPE_JSON,               /* Descriptor Type */                 
                &attr,          
                OCI_ATTR_JSON_DOM_MUTABLE,    /* Attribute type */                 
                errhp); 
if (rc != OCI_SUCCESS)  goto err_hndlr;
  
JSON記述子のOCI_ATTR_JSON_DOM_MUTABLEFALSEを設定するには、OCIAttrSet()を使用します。
OCIアプリケーションでは、次のタイプのJSON入力を記述子に設定することもできます。
  • バッファ入力 – oratext*
  • ストリーミング入力 – orastream *

23.3 JSONデータの書込みおよび読取りのコール順序

この項では、データベースへのJSONデータの書込みおよび読取りのコール順序について説明します。

データベースにJSONデータを書き込むためのコール順序を次の図に示します。

図23-2 JSONデータを書き込むためのコール順序

図23-2の説明が続きます
「図23-2 JSONデータを書き込むためのコール順序」の説明
データベースのJSONデータを読み取るためのコール順序を次の図に示します。

図23-3 JSONデータを読み取るためのコール順序

図23-3の説明が続きます
「図23-3 JSONデータを読み取るためのコール順序」の説明

23.4 JSON DOMの操作

この項では、様々なJSON DOMの操作について説明します。

23.4.1 スカラー型のマッピング

この項では、サポートされるスカラー型のマッピングをリストして説明します。

テキストJSONでは、標準JSON言語のスカラー型のみがサポートされます。ただし、JSONデータのSQL型がJSONの場合、Oracle Databaseでは標準のJSON言語型のセットが拡張されており、SQLスカラー・データ型(binary、date、timestamp、double、float、year-month interval、day-second intervalなど)に直接対応する複数のスカラー型が含まれています。

これと同様に、プログラム変数は拡張スカラー型に直接マッピングされるため、OCIJsonを使用してデータベースからJSON値がフェッチされます。これらのプリミティブ型のマッピングのサマリーを次の表に示します。

表23-2 スカラー型のマッピング

JSONスカラー型 データベースのSQL型 OCI SQL型定数 OCIプログラム変数 JSONスカラー型定数

String

VARCHAR2

SQLT_CHR

oratext[n] JZNVAL_STRING

Number

NUMBER

SQLT_VNU

OCINumber* JZNVAL_ORA_NUMBER

True (Boolean)

該当なし

該当なし 該当なし JZNVAL_TRUE

False (Boolean)

該当なし 該当なし 該当なし JZNVAL_FALSE

Binary

RAW

SQLT_BIN

ub1[n] JZNVAL_BINARY

Double

BINARY_DOUBLE

SQLT_BDOUBLE

double JZNVAL_DOUBLE

Float

BINARY_FLOAT SQLT_BFLOAT float JZNVAL_FLOAT

Date

DATE

SQLT_ODT

JsonDateTime* JZNVAL_ORA_DATE

Timestamp

TIMESTAMP

SQLT_TIMESTAMP

JsonDateTime* JZNVAL_ORA_TIMESTAMP

Day-Second Interval

INTERVALDAYTOSECOND

SQLT_INTERVAL_DS

JsonDayInterval* JZNVAL_ORA_DAYSECOND_DUR

Year-Month Interval

INTERVALYEARTOMONTH

SQLT_INTERVAL_YM

JsonYearInterval* JZNVAL_ORA_YEARMONTH_DUR

Null

該当なし 該当なし 該当なし JZNVAL_NULL

23.4.2 JSON DOMスカラー・ノードの読取り

この項では、JSON DOMスカラー・ノードを読み取る方法について説明します。

OCIは、バイナリの単精度浮動小数点と倍精度浮動小数点をIEEEの単精度浮動小数点形式および倍精度浮動小数点形式に暗黙的に変換します。number、timestamp、day-second intervalおよびyear-month interval型の場合は、JsonOCIVal *型を使用し、JsonDomGetScalarInfoOci()関数を使用して構造化された形式でデータを読み取ることができます。JsonOCIVal *は、次のコード・スニペットに示されているように定義されたCの共用体です。

/* Auxiliary Union of helper structures */
typedef union JsonOCIVal
{
  JsonDateTime      dt_JsonOCIVal;
  JsonDayInterval   dayInv_JsonOCIVal;
  JsonYearInterval  yrInv_JsonOCIVal;
  ub1               num_JsonOCIVal[JZN_ORA_NUM_MAX_LEN];
} JsonOCIVal;
次のコード・スニペットは、JSON DOMからスカラー・ノードの値をフェッチする方法を示しています。

#include <ocijson.h>
void introspectDomNode(appctx         *c,
                       JsonDomDoc     *doc,
                       JsonDomNode    *node)
{
  JsonOCIVal     av;
  jznScalarVal   sval;
  jznnodetype    ntype;
  /* Check the JSON node type */
  ntype =  JsonDomGetNodeType(doc, node);
  if (ntype == JZNDOM_SCALAR)
  {
    /* Get information for this JSON scalar node */
    JsonDomGetScalarInfoOci(doc, (JsonDomScalar *)node, &sval, &av);
    printScalarInfo(c, &sval, &av);
  }
  else if (ntype == JZNDOM_ARRAY) {...}
  else if (ntype == JZNDOM_OBJECT) {...}
  ...
}
void printScalarInfo(appctx          *c, 
                     jznScalarVal    *sval,
                     JsonOCIVal      *av)
{
  jznvaltype       vtype = sval->type_jznScalarVal;
  ub4              i;
  switch (vtype)
  {
    case JZNVAL_STRING:
      printf("Type: JZNVAL_STRING\n");
      printf("Value: %.*s\n", sval->len_jznScalarVal, sval->val_jznScalarVal);
    break;
    case JZNVAL_BINARY:
      printf("Type: JZNVAL_BINARY\n");
      printf("Value: ");
      for (i = 0 ; i < sval->binlen_jznScalarVal; i++)
        printf("%X", (sval->binval_jznScalarVal)[i]);
      printf("\n");
    break;
    case JZNVAL_FLOAT:
      printf("Type: JZNVAL_FLOAT\n");
      printf("Value: %f\n", sval->flt_jznScalarVal);
    break;
    case JZNVAL_DOUBLE:
      printf("Type: JZNVAL_DOUBLE\n");
      printf("Value: %lf\n", sval->db_jznScalarVal);
    break;
    case JZNVAL_TRUE:
      printf("Type: JZNVAL_TRUE\n");
    break;
    case JZNVAL_FALSE:
      printf("Type: JZNVAL_FALSE\n");
    break;
    case JZNVAL_NULL:
      printf("Type: JZNVAL_NULL\n");
    break;
    case JZNVAL_ORA_NUMBER:
    {
      ub4  nval;
      printf("Type: JZNVAL_ORA_NUMBER\n");
      OCINumberToInt(c->errhp, (const OCINumber *) av, (uword) sizeof(ub4), 
                     OCI_NUMBER_UNSIGNED, &nval);
      printf("Value: %d\n", nval);
      break;
    }
    case JZNVAL_ORA_DATE:
    {
      JsonDateTime  *ts;
      printf("Type: JZNVAL_ORA_DATE\n");
      ts = &(av->dt_JsonOCIVal);
      printf("Value: %d-%d-%d\n", ts->year_JsonDateTime, ts->month_JsonDateTime,
             ts->day_JsonDateTime);
      break;
    }
    case JZNVAL_ORA_TIMESTAMP:
    {
      JsonDateTime  *ts;
      printf("Type: JZNVAL_ORA_TIMESTAMP\n");
      ts = &(av->dt_JsonOCIVal);
      printf("Value: %d-%d-%d %d:%d:%d:%d\n", ts->year_JsonDateTime,
             ts->month_JsonDateTime, ts->day_JsonDateTime, ts->hour_JsonDateTime,
             ts->minute_JsonDateTime, ts->second_JsonDateTime,
             ts->fsecond_JsonDateTime);
      break;
    }
    case JZNVAL_ORA_YEARMONTH_DUR:
    {
      JsonYearInterval  *yint;
      printf("Type: JZNVAL_ORA_YEARMONTH_DUR\n");
      yint = &(av->yrInv_JsonOCIVal);
      printf("Value: %dY-%dM\n",
             yint->years_JsonYearInterval, yint->months_JsonYearInterval);
      break;
    }
    case JZNVAL_ORA_DAYSECOND_DUR:
    {
      JsonDayInterval *dint;
      printf("Type: JZNVAL_ORA_DAYSECOND_DUR\n");
      dint = &(av->dayInv_JsonOCIVal);
      printf("Value: %dD-%dH-%dM-%dS-%dSS\n",
             dint->days_JsonDayInterval, dint->hours_JsonDayInterval,
             dint->minutes_JsonDayInterval, dint->seconds_JsonDayInterval,
             dint->fseconds_JsonDayInterval);
      break;
    }
    default:
      printf("ERROR: Unsupported value type encountered [%d]\n", vtype);
    break;
  }
}

23.4.3 JSON DOMの作成

この項では、JSON DOMの作成方法について説明します。

23.4.3.1 JSONスカラー型とスカラー・コンストラクタ

この項では、JSONスカラー型および対応するスカラー・コンストラクタを示します。

可変DOMでは、新しいノードを追加したり、既存のノードを変更したりできます。次の表に、スカラー型およびその対応するコンストラクタ関数を示します。

表23-3 スカラー型とコンストラクタ

JSONスカラー型 スカラー・コンストラクタ

JZNVAL_STRING

JsonDomCreateString

JZNVAL_ORA_NUMBER

JsonDomCreateOCINumber

JZNVAL_TRUE

JsonDomCreateBoolean

JZNVAL_FALSE

JsonDomCreateBoolean

JZNVAL_BINARY

JsonDomCreateBinary

JZNVAL_DOUBLE

JsonDomCreateDouble

JZNVAL_FLOAT

JsonDomCreateFloat

JZNVAL_ORA_DATE

JsonDomCreateOCIDate

JZNVAL_ORA_TIMESTAMP

JsonDomCreateOCIDateTime

JZNVAL_NULL

JsonDomCreateNull

非スカラー型のコンストラクタ関数

次の表に、非スカラー型(配列およびオブジェクト)のコンストラクタ関数を示します。
JSONノード・タイプ スカラー・コンストラクタ
JZNDOM_ARRAY JsonDomCreateArray
JZNDOM_OBJECT JsonDomCreateObject

注意:

JsonDomSetField()関数を使用すると、指定したフィールドの値をDOM内の指定したオブジェクトに設定できます。

関連項目:

JSON DOM関数
23.4.3.2 スカラー・ノードを使用したDOMの作成

この項では、様々な型のスカラー・ノードを使用してDOMを作成する方法について説明します。

次のコードは様々な型のスカラー・ノードを使用してDOMを作成する方法を示しており、このコードは次の操作を実行します。
  1. JSON記述子を割り当て、可変プロパティを設定します
  2. 様々な型(string、number、boolean、binary、double、float、date、timestamp、null)のスカラー・ノードを作成し、これらのノードをDOMに追加します
  3. 記述子にJSON DOMコンテナを設定します
  4. JSON記述子をテキストにシリアライズします
  5. 記述子を解放します

#include <ocijson.h>
sword buildDom(appctx          *c,
               JsonDomDoc      *jdoc,
               JsonDomObject   *root,
               boolean          ismut)
{
  OCIJson        *jsond;
  oratext         outbuf[1024] = {0};
  oraub8          outlen       = 1024;
  JsonDomScalar  *node;
  /* Field names */
  oratext        *s_name     = (oratext *) "string_val";
  oratext        *n_name     = (oratext *) "number_val";
  oratext        *bt_name    = (oratext *) "true_val";
  oratext        *bf_name    = (oratext *) "false_val";
  oratext        *b_name     = (oratext *) "binary_val";
  oratext        *d_name     = (oratext *) "double_val";
  oratext        *f_name     = (oratext *) "float_val";
  oratext        *dt_name    = (oratext *) "date_val";
  oratext        *dtt_name   = (oratext *) "datetime_val";
  oratext        *nl_name    = (oratext *) "null_val";
  /* Values */
  oratext        *sval    = (oratext *) "Strings are sequence of characters";
  ub4             slen    = (ub4) strlen(sval);
  int             inval   = -29873546;
  OCINumber       nval;
  boolean         btval   = TRUE;
  boolean         bfval   = FALSE;
  ub1             bval[8] = {0x000D, 0x000E, 0x000A, 0x000D,
                             0x000B, 0x000E, 0x000E, 0x000F};
  ub4             blen    = (ub4) 8;
  double          dval    = 34837749.5699837;
  float           fval    = -133424.75;
  OCIDate        *odval   = NULL;
  sb2             yrval   = 2020;
  ub1             mnval   = 10;
  ub1             dyval   = 25;
  OCIDateTime    *odtval  = NULL;
  ub1             hrval   = 8;
  ub1             minval  = 32;
  ub1             secval  = 56;
  ub4             fsecval = 123456789;
  /* (1) Allocate JSON descriptor and set mutable property */
  checkerr("Allocate JSON descriptor", c,
            OCIDescriptorAlloc(c->envhp, (void **) &jsond,
                                OCI_DTYPE_JSON, 0, 0));
  checkerr("Attr set mutable", c,
            OCIAttrSet((void *) jsond, OCI_DTYPE_JSON, &ismut, 0,
                        OCI_ATTR_JSON_DOM_MUTABLE, c->errhp));
  /* (2) Create scalar fields and add to DOM */
  /* (a) Add string field */
  node = JsonDomCreateString(jdoc, sval, slen);
  JsonDomSetField(jdoc, root, s_name, (ub2) strlen(s_name),
                  (JsonDomNode *) node);
  /* (b) Add number field */
  checkerr("Create OCINumber", c,
           OCINumberFromInt(c->errhp, &inval, (uword) sizeof(int),
                            (uword) OCI_NUMBER_SIGNED, &nval));
  node = JsonDomCreateOCINumber(jdoc, &nval);
  JsonDomSetField(jdoc, root, n_name, (ub2) strlen(n_name),
                  (JsonDomNode *) node);
  /* (c) Add boolean TRUE field */
  node = JsonDomCreateBoolean(jdoc, btval);
  JsonDomSetField(jdoc, root, bt_name, (ub2) strlen(bt_name),
                  (JsonDomNode *) node);
  /* (d) Add boolean FALSE field */
  node = JsonDomCreateBoolean(jdoc, bfval);
  JsonDomSetField(jdoc, root, bf_name, (ub2) strlen(bf_name),
                  (JsonDomNode *) node);
  /* (e) Add binary field */
  node = JsonDomCreateBinary(jdoc, bval, blen);
  JsonDomSetField(jdoc, root, b_name, (ub2) strlen(b_name),
                  (JsonDomNode *) node);
  /* (f) Add double field */
  node = JsonDomCreateDouble(jdoc, dval);
  JsonDomSetField(jdoc, root, d_name, (ub2) strlen(d_name),
                  (JsonDomNode *) node);
  /* (g) Add float field */
  node = JsonDomCreateFloat(jdoc, fval);
  JsonDomSetField(jdoc, root, f_name, (ub2) strlen(f_name),
                  (JsonDomNode *) node);
  /* (h) Add date field */
  checkerr("Create OCIDate", c,
           OCIDescriptorAlloc(c->envhp, (void **) &odval, OCI_DTYPE_DATE,
                              0, NULL));
  OCIDateSetDate(odval, yrval, mnval, dyval);
  node = JsonDomCreateOCIDate(jdoc, odval);
  JsonDomSetField(jdoc, root, dt_name, (ub2) strlen(dt_name),
                  (JsonDomNode *) node);
  /* (i) Add datetime field */
  checkerr("Create OCIDateTime", c,
           OCIDescriptorAlloc(c->envhp, (void **) &odtval, OCI_DTYPE_TIMESTAMP,
                              0, NULL));
  OCIDateTimeConstruct (c->envhp, c->errhp, odtval,
                        yrval-10, mnval-5, dyval-10,
                        hrval, minval, secval, fsecval, NULL, 0);
  node = JsonDomCreateOCIDateTime(jdoc, odtval);
  JsonDomSetField(jdoc, root, dtt_name, (ub2) strlen(dtt_name),
                  (JsonDomNode *) node);
  /* (j) Add NULL field */
  node = JsonDomCreateNull(jdoc);
  JsonDomSetField(jdoc, root, nl_name, (ub2) strlen(nl_name),
                  (JsonDomNode *) node);
   /* (3) Set the JSON DOM container in the descriptor */
  checkerr("Set JSON DOM container", c,
            OCIJsonDomDocSet(c->svchp, jsond, jdoc, c->errhp, 0));
  /* (4) Serialize JSON descriptor to text */
  checkerr("To Text Buffer", c,
            OCIJsonToTextBuffer(c->svchp, jsond, outbuf, &outlen,
                                JZNU_PRINT_PRETTY, c->errhp,
                                OCI_JSON_TEXT_ENV_NLS));
  printf("Descriptor content:\n");
  printf("%.*s \n", outlen, outbuf);
finally:
  /* (5) Free the descriptors */
  if (odval)
    checkerr("Free Date descriptor", c,
              OCIDescriptorFree(odval, OCI_DTYPE_DATE));
  if (odtval)
    checkerr("Free DateTime descriptor", c,
              OCIDescriptorFree(odtval, OCI_DTYPE_TIMESTAMP));
  if (jsond)
    checkerr("Free JSON descriptor", c,
              OCIDescriptorFree(jsond, OCI_DTYPE_JSON));
  return c->status;
}
OCIJsonToTextBuffer()関数は、次のテキストJSONの出力を戻します。
{
  "string_val" : "Strings are sequence of characters",
  "number_val" : -29873546,
  "true_val" : true,
  "false_val" : false,
  "binary_val" : "0D0E0A0D0B0E0E0F",
  "double_val" : 34837749.5699837,
  "float_val" : -133424.75,
  "date_val" : "2020-10-25T00:00:00",
  "datetime_val" : "2010-05-15T08:32:56.123456789",
  "null_val" : null
}

関連項目:

JSON DOM関数

23.5 JSON記述子を使用したマルチスレッド

OCIJson*記述子はスレッド・セーフではありません。記述子とその子孫のDOMノードが一度に1つのスレッドのみで操作されるようにすることは、ユーザーの責任です。

23.6 キャラクタ・セットの処理

OCIでは、テキスト入力のキャラクタ・セットは、OCI環境ハンドルの設定によって異なります。

OCIEnv*の作成時にユーザーがcsidパラメータを指定しない場合、NLS_LANG設定がハンドルのデフォルト設定として使用されます。テキストJSONが入力であるOCIJsonTextBufferParse()OCIJsonTextStreamParse()などのAPIの場合、入力は、Oracleで認識される任意のキャラクタ・セットにすることができ(JSON構文に準拠している場合)、環境ハンドルまたはNLS_LANGパラメータに設定されているキャラクタ・セットである必要はありません。

注意:

Unicodeでエンコードされた入力にJZN_INPUT_DETECTを使用する場合、入力のエンコーディングはUTF-8、UTF-16 (LEまたはBE)のいずれかとして検出され、それに応じて処理されます。テキストJSONの入力がこのモードのUnicodeのエンコーディングのいずれかではない場合は、ユーザー・エラーであり、動作は保証されません。

OCI_JSON_TEXT_ENV_NLSモードが設定されている場合を除き、OCIJson API (OCIJsonToTextBuffer ()OCIJsonToTextStream ()など)は、テキストJSONをAL32UTF8キャラクタ・セットで戻します。