Click here to view and discuss this page in DocCommentXchange. In the future, you will be sent there automatically.

SQL Anywhere 12.0.1 » SQL Anywhere サーバー プログラミング » SQL Anywhere 外部環境のサポート

 

ESQL 外部環境と ODBC 外部環境

これまで SQL Anywhere では、C または C++ で記述されたコンパイル済みネイティブ関数を呼び出すことができました。ただし、これらのプロシージャーがデータベースサーバーで実行されるとき、ダイナミックリンクライブラリまたは共有オブジェクトが常にデータベースサーバーによってロードされ、ネイティブ関数への呼び出しがデータベースサーバーによって行われていました。データベースサーバーでこれらのネイティブ関数の呼び出しを行うと効率が最も良くなる一方で、ネイティブ関数が誤動作した場合は、重大な結果を招きかねません。特に、ネイティブ関数が無限ループに入った場合は、データベースサーバーがハングする可能性があります。また、ネイティブ関数が原因で障害が発生した場合は、データベースサーバーがクラッシュする可能性があります。このため、データベースサーバーの外部環境でコンパイル済みネイティブ関数を実行するオプションが導入されました。コンパイル済みネイティブ関数を外部環境で実行することには、次のような大きなメリットがあります。

  1. コンパイル済みネイティブ関数が誤作動した場合でも、データベースサーバーはハングまたはクラッシュしない。

  2. ODBC、Embedded SQL (ESQL)、または SQL Anywhere C API を使用するようネイティブ関数を作成可能で、データベースサーバーに接続せずにサーバー側の呼び出しをデータベースサーバーに戻すことができる。

  3. ネイティブ関数は結果セットをデータベースサーバーに返すことができる。

  4. 外部環境では、32 ビットのデータベースサーバーが 64 ビットのコンパイル済みネイティブ関数と通信できる。また、その逆も可能である。コンパイル済みネイティブ関数がデータベースサーバーのアドレス空間に直接ロードされた場合、これは不可能です。32 ビットのライブラリは 32 ビットのサーバー、64 ビットのライブラリは 64 ビットのサーバーでしかロードできません。

コンパイル済みネイティブ関数を外部環境で実行した場合、データベースサーバー内で実行した場合よりも多少パフォーマンスが低下します。

また、ネイティブ関数と情報の受け渡しをするためには、コンパイル済みネイティブ関数はネイティブ関数呼び出しインターフェイスを使用する必要があります。このインターフェイスについては、SQL Anywhere 外部呼び出しインターフェイスを参照してください。

コンパイル済みネイティブ C 関数をデータベースサーバー内でなく外部環境で実行するには、ストアドプロシージャーまたはファンクションを EXTERNAL NAME 句で定義し、後続の LANGUAGE 属性で C_ESQL32、C_ESQL64、C_ODBC32、C_ODBC64 のいずれか 1 つを指定します。

Perl、PHP、および Java の外部環境とは異なり、データベースにソースコードやコンパイル済みオブジェクトはインストールしません。したがって、ESQL および ODBC の外部環境を使用する前に INSTALL 文を実行する必要がありません。

次の例に示す C++ で記述された関数は、データベースサーバー内でも外部環境内でも実行できます。



#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "extfnapi.h"
    
BOOL APIENTRY DllMain( HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    return TRUE;
}

// Note: extfn_use_new_api used only for
// execution in the database server

extern "C" __declspec( dllexport )
a_sql_uint32 extfn_use_new_api( void )
{
    return( EXTFN_API_VERSION );
}
    
extern "C" __declspec( dllexport )
void SimpleCFunction(
    an_extfn_api *api,
    void *arg_handle )
{
    short               result;
    an_extfn_value      arg;
    an_extfn_value      retval;
    int *               intptr;
    int                 i, j, k;
    
    j = 1000;
    k = 0;
    for( i = 1; i <= 4; i++ )
    {
        result = api->get_value( arg_handle, i, &arg );
        if( result == 0 || arg.data == NULL ) break;
        if( arg.type & DT_TYPES != DT_INT ) break;
        intptr = (int *) arg.data;
        k += *intptr * j;
        j = j / 10;
    }
    retval.type = DT_INT;
    retval.data = (void*)&k;
    retval.piece_len = retval.len.total_len = 
       (a_sql_uint32) sizeof( int );
    api->set_value( arg_handle, 0, &retval, 0 );    
    return;
}

この関数をダイナミックリンクライブラリまたは共有オブジェクトにコンパイルすると、外部環境から呼び出すことができます。dbexternc12 という実行イメージファイルがデータベースサーバーによって開始され、この実行イメージファイルがダイナミックリンクライブラリまたは共有オブジェクトをロードします。この実行ファイルについては、さまざまなバージョンが SQL Anywhere に含まれています。たとえば Windows では、32 ビットと 64 ビットの実行ファイルがあります。

32 ビットまたは 64 ビットバージョンのデータベースサーバーを使用でき、どちらのバージョンのデータベースサーバーでも 32 ビットまたは 64 ビットバージョンの dbexternc12 を開始できます。これは、外部環境を使用するメリットの 1 つです。データベースサーバーによって開始された dbexternc12 は、接続が切断されるか STOP EXTERNAL ENVIRONMENT 文が (正しい環境名で) 実行されるまで終了しません。外部環境呼び出しを実行する接続には、dbexternc12 のコピーがそれぞれ与えられます。

コンパイル済みネイティブ関数 SimpleCFunction を呼び出すには、次のようにラッパーを定義します。

CREATE FUNCTION SimpleCDemo( 
  IN arg1 INT, 
  IN arg2 INT, 
  IN arg3 INT, 
  IN arg4 INT )
RETURNS INT
EXTERNAL NAME 'SimpleCFunction@c:\\c\\extdemo.dll' 
LANGUAGE C_ODBC32;

これは、コンパイル済みネイティブ関数をデータベースサーバーのアドレス空間にロードする場合の記述方法と、ほとんど同じです。ただ 1 つ異なるのは、LANGUAGE C_ODBC32 句を使用することです。この句は、SimpleCDemo が外部環境で実行される関数であり、32 ビットの ODBC 呼び出しを使用することを指定しています。C_ESQL32、C_ESQL64、C_ODBC32、C_ODBC64 の言語の指定は、サーバー側の要求を作成するときに、外部 C 関数で 32 ビットまたは 64 ビットの ODBC 呼び出し、ESQL 呼び出し、または SQL Anywhere C API 呼び出しのどれを行うのかをデータベースサーバーに知らせます。

サーバー側の要求を作成する際にネイティブ関数が ODBC 呼び出し、ESQL 呼び出し、SQL Anywhere C API 呼び出しのいずれも使用しない場合は、32 ビットのアプリケーションには C_ODBC32 または C_ESQL32 を、64 ビットのアプリケーションには C_ODBC64 または C_ESQL64 を使用できます。上記の外部 C 関数はこれに該当します。この関数はこれらの API を一切使用しません。

サンプルのコンパイル済みネイティブ関数を実行するには、次の文を実行します。

SELECT SimpleCDemo(1,2,3,4);

サーバー側の ODBC を使用するには、C/C++ コードでデフォルトのデータベース接続を使用する必要があります。データベース接続のハンドルを取得するには、EXTFN_CONNECTION_HANDLE_ARG_NUM 引数を指定して get_value を呼び出します。この引数は、新しい外部環境接続を開くのではなく、現在の外部環境接続を返すようにデータベースサーバーに伝えます。



#include <windows.h>
#include <stdio.h>
#include "odbc.h"
#include "extfnapi.h"
    
BOOL APIENTRY DllMain( HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    return TRUE;
}
    
extern "C" __declspec( dllexport ) 
void ServerSideFunction( an_extfn_api *api, void *arg_handle )
{
    short               result;
    an_extfn_value      arg;
    an_extfn_value      retval;
    SQLRETURN           ret;

    ret = -1;
    // set up the return value struct
    retval.type = DT_INT;
    retval.data = (void*) &ret;
    retval.piece_len = retval.len.total_len =
        (a_sql_uint32) sizeof( int );

    result = api->get_value( arg_handle,
                    EXTFN_CONNECTION_HANDLE_ARG_NUM,
                    &arg );
    if( result == 0 || arg.data == NULL )
    {
        api->set_value( arg_handle, 0, &retval, 0 );
        return;
    }

    HDBC dbc = (HDBC)arg.data;
    HSTMT stmt = SQL_NULL_HSTMT;
    ret = SQLAllocHandle( SQL_HANDLE_STMT, dbc, &stmt );
    if( ret != SQL_SUCCESS ) return;
    ret = SQLExecDirect( stmt,
            (SQLCHAR *) "INSERT INTO odbcTab "
                "SELECT table_id, table_name "
                "FROM SYS.SYSTAB", SQL_NTS );
    if( ret == SQL_SUCCESS )
    {
        SQLExecDirect( stmt,
            (SQLCHAR *) "COMMIT", SQL_NTS );
    }
    SQLFreeHandle( SQL_HANDLE_STMT, stmt );
    
    api->set_value( arg_handle, 0, &retval, 0 );
    return;
}

上記の ODBC コードが extodbc.cpp ファイルに保存されている場合、次のコマンドを使用して Windows 用に構築できます (SQL Anywhere ソフトウェアがフォルダー c:\sa12 にインストールされており、Microsoft Visual C++ もインストールされていることが前提です)。

cl extodbc.cpp /LD /Ic:\sa12\sdk\include odbc32.lib

次の例では、テーブルを作成し、コンパイル済みネイティブ関数を呼び出すストアドプロシージャーのラッパーを定義してから、ネイティブ関数を呼び出してテーブルにデータを移植します。



CREATE TABLE odbcTab(c1 int, c2 char(128));

CREATE FUNCTION ServerSideODBC( )
RETURNS INT
EXTERNAL NAME 'ServerSideFunction@extodbc.dll'
LANGUAGE C_ODBC32;

SELECT ServerSideODBC();

// The following statement should return two identical rows
SELECT COUNT(*) FROM odbcTab 
UNION ALL 
SELECT COUNT(*) FROM SYS.SYSTAB;

同様に、サーバー側の ESQL を使用するには、C/C++ コードでデフォルトのデータベース接続を使用する必要があります。データベース接続のハンドルを取得するには、EXTFN_CONNECTION_HANDLE_ARG_NUM 引数を指定して get_value を呼び出します。この引数は、新しい外部環境接続を開くのではなく、現在の外部環境接続を返すようにデータベースサーバーに伝えます。



#include <windows.h>
#include <stdio.h>

#include "sqlca.h"
#include "sqlda.h"
#include "extfnapi.h"

BOOL APIENTRY DllMain( HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    return TRUE;
}

EXEC SQL INCLUDE SQLCA;
static SQLCA *_sqlc;
EXEC SQL SET SQLCA "_sqlc";
EXEC SQL WHENEVER SQLERROR { ret = _sqlc->sqlcode; };

extern "C" __declspec( dllexport ) 
void ServerSideFunction( an_extfn_api *api, void *arg_handle )
{
    short               result;
    an_extfn_value      arg;
    an_extfn_value      retval;

    EXEC SQL BEGIN DECLARE SECTION;
    char *stmt_text =
        "INSERT INTO esqlTab "
            "SELECT table_id, table_name "
            "FROM SYS.SYSTAB";
    char *stmt_commit =
        "COMMIT";
    EXEC SQL END DECLARE SECTION;

    int ret = -1;

    // set up the return value struct
    retval.type = DT_INT;
    retval.data = (void*) &ret;
    retval.piece_len = retval.len.total_len =
        (a_sql_uint32) sizeof( int );

    result = api->get_value( arg_handle,
                    EXTFN_CONNECTION_HANDLE_ARG_NUM,
                    &arg );
    if( result == 0 || arg.data == NULL )
    {
        api->set_value( arg_handle, 0, &retval, 0 );
        return;
    }
    ret = 0;
    _sqlc = (SQLCA *)arg.data;

    EXEC SQL EXECUTE IMMEDIATE :stmt_text;
    EXEC SQL EXECUTE IMMEDIATE :stmt_commit;

    api->set_value( arg_handle, 0, &retval, 0 );
}

上記の Embedded SQL 文が extesql.sqc ファイルに保存されている場合、次のコマンドを使用して Windows 用に構築できます (SQL Anywhere ソフトウェアがフォルダー c:\sa12 にインストールされており、Microsoft Visual C++ もインストールされていることが前提です)。

sqlpp extesql.sqc extesql.cpp
cl extesql.cpp /LD /Ic:\sa12\sdk\include c:\sa12\sdk\lib\x86\dblibtm.lib

次の例では、テーブルを作成し、コンパイル済みネイティブ関数を呼び出すストアドプロシージャーのラッパーを定義してから、ネイティブ関数を呼び出してテーブルにデータを移植します。



CREATE TABLE esqlTab(c1 int, c2 char(128));

CREATE FUNCTION ServerSideESQL( )
RETURNS INT
EXTERNAL NAME 'ServerSideFunction@extesql.dll'
LANGUAGE C_ESQL32;

SELECT ServerSideESQL();

// The following statement should return two identical rows
SELECT COUNT(*) FROM esqlTab 
UNION ALL 
SELECT COUNT(*) FROM SYS.SYSTAB;

前述の例のように、サーバー側の SQL Anywhere C API 呼び出しを使用するには、C/C++ コードでデフォルトのデータベース接続を使用する必要があります。データベース接続のハンドルを取得するには、EXTFN_CONNECTION_HANDLE_ARG_NUM 引数を指定して get_value を呼び出します。この引数は、新しい外部環境接続を開くのではなく、現在の外部環境接続を返すようにデータベースサーバーに伝えます。次の例は、接続ハンドルを取得し、C API 環境を初期化し、接続ハンドルを SQL Anywhere C API で使用できる接続オブジェクト (a_sqlany_connection) に変換するためのフレームワークを示したものです。



#include <windows.h>
#include "sacapidll.h"
#include "extfnapi.h"

BOOL APIENTRY DllMain( HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
    )
{
    return TRUE;
}

extern "C" __declspec( dllexport )
void ServerSideFunction( an_extfn_api *extapi, void *arg_handle )
{
    short               result;
    an_extfn_value      arg;
    an_extfn_value      retval;
    unsigned            offset;
    char                *cmd;
    
    SQLAnywhereInterface  capi;
    a_sqlany_connection * sqlany_conn;
    unsigned int          max_api_ver;

    result = extapi->get_value( arg_handle,
                    EXTFN_CONNECTION_HANDLE_ARG_NUM,
                    &arg );

    if( result == 0 || arg.data == NULL )
    {
        return;
    }
    if( !sqlany_initialize_interface( &capi, NULL ) )
    {
        return;
    } 
    if( !capi.sqlany_init( "MyApp",
            SQLANY_CURRENT_API_VERSION,
            &max_api_ver ) )
    {
        sqlany_finalize_interface( &capi );
        return;
    }
    sqlany_conn = sqlany_make_connection( arg.data );

    // processing code goes here

    capi.sqlany_fini();

    sqlany_finalize_interface( &capi );
    return;
}

上記の C コードが extcapi.c ファイルに保存されている場合、次のコマンドを使用して Windows 用に構築できます (SQL Anywhere ソフトウェアがフォルダー c:\sa12 にインストールされており、Microsoft Visual C++ もインストールされていることが前提です)。

cl /LD /Tp extcapi.c /Tp c:\sa12\SDK\C\sacapidll.c
      /Ic:\sa12\SDK\Include c:\sa12\SDK\Lib\X86\dbcapi.lib

次の例では、コンパイル済みネイティブ関数を呼び出すストアドプロシージャーのラッパーを定義してから、ネイティブ関数を呼び出します。

CREATE FUNCTION ServerSideC()
RETURNS INT
EXTERNAL NAME 'ServerSideFunction@extcapi.dll'
LANGUAGE C_ESQL32;

SELECT ServerSideC();

上記の例の LANGUAGE 属性では C_ESQL32 を指定しています。64 ビットのアプリケーションの場合は C_ESQL64 を使用します。SQL Anywhere C API は ESQL と同じレイヤー (ライブラリ) に構築されているため、Embedded SQL の LANGUAGE 属性を使用する必要があります。

前述のとおり、外部環境呼び出しを実行する接続は、dbexternc12 のコピーをそれぞれ開始します。この実行可能アプリケーションは、最初の外部環境呼び出しが実行される際にサーバーによって自動的にロードされます。ただし、START EXTERNAL ENVIRONMENT 文を使用して dbexternc12 をプリロードすることもできます。外部環境呼び出しを初めて実行する際のわずかな遅延を回避したい場合には便利です。次に、この文の例を示します。

START EXTERNAL ENVIRONMENT C_ESQL32

dbexternc12 のプリロードは、外部関数をデバッグする場合も便利です。デバッガーを使用して実行中の dbexternc12 プロセスにアタッチし、外部関数にブレークポイントを設定できます。

STOP EXTERNAL ENVIRONMENT 文は、ダイナミックリンクライブラリや共有オブジェクトを更新する場合に便利です。現在の接続でネイティブライブラリローダの dbexternc12 を終了し、ダイナミックリンクライブラリや共有オブジェクトへのアクセスを解放します。複数の接続が同じダイナミックリンクライブラリまたは共有オブジェクトを使用している場合は、dbexternc12 の各コピーを終了する必要があります。STOP EXTERNAL ENVIRONMENT 文には、適切な外部環境名を指定する必要があります。次に、この文の例を示します。

STOP EXTERNAL ENVIRONMENT C_ESQL32

外部関数から結果セットを返すためには、コンパイル済みネイティブ関数はネイティブ関数呼び出しインターフェイスを使用する必要があります。このインターフェイスの詳細については、SQL Anywhere 外部呼び出しインターフェイスを参照してください。

次のコードフラグメントは、結果セット情報の構造体を設定する方法を示したものです。カラムカウント、カラム情報の構造体の配列へのポインター、カラムデータ値の構造体の配列へのポインターを含んでいます。この例は、SQL Anywhere C API も使用しています。



an_extfn_result_set_info    rs_info;

int columns = capi.sqlany_num_cols( sqlany_stmt );

an_extfn_result_set_column_info *col_info =
    (an_extfn_result_set_column_info *)
    malloc( columns * sizeof(an_extfn_result_set_column_info) );

an_extfn_result_set_column_data *col_data =
    (an_extfn_result_set_column_data *)
    malloc( columns * sizeof(an_extfn_result_set_column_data) );

rs_info.number_of_columns   = columns;
rs_info.column_infos        = col_info;
rs_info.column_data_values  = col_data;

次のコードフラグメントは、結果セットを記述する方法を示したものです。SQL Anywhere C API を使用して、C API によって実行された SQL クエリのカラム情報を取得します。SQL Anywhere C API から取得した各カラムの情報は、カラムの名前、型、幅、インデックス、および NULL 値インジケーターに変換され、結果セットの記述に使用されます。



a_sqlany_column_info        info;
for( int i = 0; i < columns; i++ )
{
    if( sqlany_get_column_info( sqlany_stmt, i, &info ) )
    {
        // set up a column  description
        col_info[i].column_name  = info.name;
        col_info[i].column_type  = info.native_type;
        switch( info.native_type )
        {
            case DT_DATE:       // DATE is converted to string by C API
            case DT_TIME:       // TIME is converted to string by C API
            case DT_TIMESTAMP:  // TIMESTAMP is converted to string by C API
            case DT_DECIMAL:    // DECIMAL is converted to string by C API
                col_info[i].column_type  = DT_FIXCHAR;
                break;
            case DT_FLOAT:      // FLOAT is converted to double by C API
                col_info[i].column_type  = DT_DOUBLE;
                break;
            case DT_BIT:        // BIT is converted to tinyint by C API
                col_info[i].column_type  = DT_TINYINT;
                break;
        }
        col_info[i].column_width = info.max_size;
        col_info[i].column_index = i + 1; // column indices are origin 1
        col_info[i].column_can_be_null = info.nullable;
    }
}
// send the result set description
if( extapi->set_value( arg_handle,
                    EXTFN_RESULT_SET_ARG_NUM,
                    (an_extfn_value *)&rs_info,
                    EXTFN_RESULT_SET_DESCRIBE ) == 0 )
{
    // failed
    free( col_info );
    free( col_data );
    return;
}

結果セットが記述されると、結果セットのローを返すことができます。次のコードフラグメントは、結果セットのローを返す方法を示したものです。SQL Anywhere C API を使用して、C API によって実行された SQL クエリのローをフェッチします。SQL Anywhere C API によって返されたローは、呼び出しを行った環境に 1 つずつ送り返されます。カラムデータ値の構造体の配列に格納してから、各ローを返す必要があります。カラムデータ値の構造体はカラムインデックス、データ値へのポインター、データ長、追加フラグから構成されます。



a_sqlany_data_value *value = (a_sqlany_data_value *)
    malloc( columns * sizeof(a_sqlany_data_value) );

while( capi.sqlany_fetch_next( sqlany_stmt ) )
{
    for( int i = 0; i < columns; i++ )
    {
        if( capi.sqlany_get_column( sqlany_stmt, i, &value[i] ) )
        {
            col_data[i].column_index = i + 1;
            col_data[i].column_data  = value[i].buffer;
            col_data[i].data_length  = (a_sql_uint32)*(value[i].length);
            col_data[i].append  = 0;
            if( *(value[i].is_null) )
            {
                // Received a NULL value
                col_data[i].column_data = NULL;
            }
        }
    }
    if( extapi->set_value(  arg_handle,
                        EXTFN_RESULT_SET_ARG_NUM,
                        (an_extfn_value *)&rs_info,
                        EXTFN_RESULT_SET_NEW_ROW_FLUSH ) == 0 )
    {
        // failed
        free( value );
        free( col_data );
        free( col_data );
        extapi->set_value( arg_handle, 0, &retval, 0 );
        return;
    }
}

サーバー側の要求の作成方法、および外部関数から結果セットを返す方法の詳細については、%SQLANYSAMP12%\SQLAnywhere\ExternalEnvironments\ExternC のサンプルを参照してください。

 参照