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

SQL Anywhere 12.0.0 (中文) » SQL Anywhere 服务器 - 编程 » SQL Anywhere 外部环境支持

 

ESQL 和 ODBC 外部环境

SQL Anywhere 支持调用以 C 或 C++ 语言编写的编译后本地函数已有一段时间了。但是,如果这些过程由数据库服务器运行,动态链接库或共享对象始终由数据库服务器装载,本地函数也始终由数据库服务器调用。尽管让数据库服务器进行这些本地调用是最为有效的,但如果本地函数存在问题,就可能会导致严重的后果。具体来说,如果本地函数进入一个无限循环,数据库服务器就可能会挂起,而且如果本地函数引发故障,就会使数据库服务器崩溃。因此,现在您可以选择在数据库服务器的外部(即在外部环境中)运行编译后的本地函数。在外部环境中运行编译后的本地函数有几大优点:

  1. 即使编译后的本地函数存在问题,数据库服务器也不会挂起或崩溃。

  2. 本地函数可以使用 ODBC、嵌入式 SQL (ESQL) 或 SQL Anywhere C API,而且无需建立连接即可进行返回到数据库服务器的服务器端调用。

  3. 本地函数可以将结果集返回给数据库服务器。

  4. 在外部环境中,32 位数据库服务器可以与 64 位编译后本地函数进行通信,反之亦然。请注意,如果编译后的本地函数直接装载到数据库服务器的地址空间中,则无法实现上述的通信。32 位的库只能由 32 位服务器装载,同样 64 位的库也只能由 64 位服务器装载。

在外部环境(而非数据库服务器)中运行编译后的本地函数会略微影响性能。

此外,编译后的本地函数必须使用本地函数调用接口将信息传递到本地函数,并从本地函数返回信息。此接口将在SQL Anywhere 外部调用接口中介绍。

要在外部环境而非数据库服务器中运行编译后的本地 C 函数,存储过程或函数应使用其后的 LANGUAGE 属性的 EXTERNAL NAME 子句来定义,其中,LANGUAGE 属性指定 C_ESQL32、C_ESQL64、C_ODBC32 或 C_ODBC64。

与 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。这是使用外部环境的优点之一。请注意,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;

这与在将编译后本地函数加载到数据库服务器地址空间时该函数的描述方式几乎完全相同。唯一的区别是使用了 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 调用中的任何一个执行服务器端请求,则 C_ODBC32 或 C_ESQL32 可用于 32 位应用程序,C_ODBC64 或 C_ESQL64 可用于 64 位应用程序。这是以上所示的外部 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 );
}

如果以上嵌入式 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 语言属性,因为 SQL Anywhere C API 构建在与 ESQL 相同的层(库)上。

如前所述,每个执行外部环境调用的连接都将启动其自身的 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 获得的各列信息将被转换为用于描述结果集的列名称、类型、宽度、索引以及空值指示符。



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 返回的行被发送回调用环境,一次发回一行。返回各行之前,必须先填充列数据值结构的数组。列数据值结构包括列索引、指向数据值的指针、数据长度和附加标志。



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;
    }
}

有关更详细信息,请参见SQL Anywhere 外部调用接口

有关如何发出服务器端请求以及如何从外部函数返回结果集的详细信息,请参见 samples-dir\SQLAnywhere\ExternalEnvironments\ExternC 中的示例。