Skip to main content

Make DB2 administrative APIs available through SQL

Collect statistics and load data into a table using only SQL statements

Knut Stolze (stolze@de.ibm.com), DB2 WebSphere Information Integration Development, IBM Germany
Author photo
Knut Stolze started his work with DB2 when he joined IBM as a visiting scientist at the Silicon Valley Lab where he worked on the DB2 Image Extender. For the following two years he moved on to the DB2 Spatial Extender team and was responsible for several enhancements to improve the usability, the performance, and the standard-conformance of the Extender. Currently, he works as a teaching assistant at the University of Jena, Germany, and continues his work for IBM in the area of the Information Integrator development. He can be reached through the newsgroups comp.databases.ibm-db2 and ibm.software.db2.udb.spatial or via stolze@de.ibm.com.

Summary:  Implement functions and stored procedures that allow you to use the IBM® DB2® Universal Database™ (DB2 UDB) administrative APIs using SQL. This article illustrates two stored procedures, one that calls db2Runstats to collect statistics on a given table, and another that wraps the db2Load function, allowing you to load data from a file residing on the database server using DB2's LOAD functionality. Such procedures can be invoked from any application that uses SQL to access a DB2 database, regardless of the mechanism used for the communication protocol between the application and the database server, including CLI, JDBC, SQLCJ, and embedded SQL.

Date:  19 May 2005
Level:  Intermediate
Activity:  973 views

Introduction

Does DB2 provides access to the administrative APIs through some Java or SQL functionality? Recent versions of DB2 UDB for Linux™, UNIX®, and Windows® have added several built-in functions to make, for example, snapshot functionality available through user-defined functions (UDFs). However, not all APIs can be accessed that way yet. Some APIs can only be invoked from C code, if necessary, through the use of JNI or other coupling facilities. In this article, you'll see an easy and generic way to make the APIs you need available to all your applications.

The mechanism is based on stored procedures that are implemented in C. Each API call is encapsulated in a dedicated procedure. Such a procedure takes the necessary parameters -- like the name of the table that should be handled or the file from which data shall be loaded -- as input parameters, prepares the required data structures, and calls the API.

The following sections are organized as follows:


The RUNSTATS procedure

The first stored procedure simply wraps the db2Runstats API to offer a way to call the same functionality as the DB2 command RUNSTATS. (For more information on the db2Runstats API and the RUNSTATS command, see Resources.) The procedure takes the fully-qualified name of a table as input parameter and sets up the necessary data structures that are passed to the API function. Please note that the requirement to supply a table name qualified by the name of the schema where that table was created is inheritted from the db2Runstats API and, thus, also applies to the parameter of the procedure.

The statistics are collected for all columns (with distributions) and all indexes (with extended statistics). Read access is allowed while the operation is performed. The functionality implemented in the stored procedure is equivalent to the following command:

   RUNSTATS ON TABLE <table-name>
      ON ALL COLUMNS WITH DISTRIBUTION AND
      DETAILED INDEXES ALL
      ALLOW READ ACCESS

The procedure does not perform any action if the given table name is NULL. If the db2Runstats API encounters any error (for example, the specified table does not exist or the user who called the procedure does not have the necessary privileges) the error is returned to the caller using SQLSTATE "38RS1". You can see the details of the procedure in Listing 1 below. Note that the call to the API where the actual work is done is set in italics. As you can see, the invocation itself is actually the smallest part of the implementation. Before that, though, comes the (rather long) preparation of the data structures.


Listing 1. Stored procedure code to invoke db2Runstats
#include <sql.h>
#include <sqludf.h>
#include <db2ApiDf.h>

int SQL_API_FN runstats_proc(
	SQLUDF_VARCHAR *tableName,
	SQLUDF_NULLIND *tableName_ind,
	SQLUDF_TRAIL_ARGS)
{
    SQL_API_RC rc = SQL_RC_OK;
    db2RunstatsData data;
    struct sqlca sqlca;

    if (SQLUDF_NULL(tableName_ind)) {
        goto cleanup;
    }

    /* initialize data structures */
    data.iSamplingOption = 0;
    data.piTablename = (unsigned char *)tableName;
    data.piColumnList = NULL;
    data.piColumnDistributionList = NULL;
    data.piColumnGroupList = NULL;
    data.piIndexList = NULL;
    data.iRunstatsFlags = DB2RUNSTATS_ALL_COLUMNS | DB2RUNSTATS_DISTRIBUTION |
        DB2RUNSTATS_ALL_INDEXES | DB2RUNSTATS_EXT_INDEX |
        DB2RUNSTATS_ALLOW_READ;
    data.iNumColumns = 0;
    data.iNumColdist = 0;
    data.iNumColGroups = 0;
    data.iNumIndexes = 0;
    data.iParallelismOption = 0;
    data.iTableDefaultFreqValues = -1; /* use default */
    data.iTableDefaultQuantiles = -1; /* use default */
    data.iUtilImpactPriority = 0;
    data.iSamplingRepeatable = 0; /* unused */
    memset(&sqlca, 0x00, sizeof sqlca);

    rc = db2Runstats(db2Version820, &data, &sqlca);
    if (rc != SQL_RC_OK || SQLCODE != SQL_RC_OK) {
        memcpy(SQLUDF_STATE, "38RS1", SQLUDF_SQLSTATE_LEN);
        sprintf(SQLUDF_MSGTX, "Error %d returned by db2Runstats.",
                (int)(rc == SQL_RC_OK ? SQLCODE : rc));
        goto cleanup;
    }

 cleanup:
    return SQLZ_DISCONNECT_PROC;
}

In order to use the procedure, it must be registered in the database with the following SQL statement.

Note: We use the character '@' as statement terminator throughout the article; it is not part of the statement itself.


Listing 2. SQL statement
  CREATE PROCEDURE runstats(IN tableName VARCHAR(256))
     SPECIFIC runstats
     DYNAMIC RESULT SETS 0
     MODIFIES SQL DATA
     NOT DETERMINISTIC
     CALLED ON NULL INPUT
     LANGUAGE C
     EXTERNAL NAME 'db2_api!runstats_proc'
     FENCED  THREADSAFE
     NO EXTERNAL ACTION
     PARAMETER STYLE SQL@

Increasing control over the internal operation

Of course, it is possible to allow the caller of the stored procedure to further control the execution. For example, you can influence which indexes or columns are affected by the processing. To that end, you should change the CREATE PROCEDURE statement and the C code to allow for additional parameters. Depending on the values of those parameters, you could now set the options for the data structures any way you like (for example, allowing write access during the operation).

Testing the procedure

After you compile the source code, build the shared library, and register the procedure in the database, you can verify the proper functioning of your work so far. Listing 3 demonstrates a few examples. A table is created and some data is inserted before the procedure is called. The results of the call can be verified in the catalog tables in the SYSSTAT schema. The following scenario shows collected statistics on a table of the DB2 catalog itself, the table SYSIBM.SYSTABLES.


Listing 3. Testing the RUNSTATS procedure
$ db2 -td@

db2 => CREATE TABLE stolze.t ( a INT NOT NULL, b VARCHAR(100),
db2 (cont.) => PRIMARY KEY(a) )@
db2 => INSERT INTO stolze.t VALUES (1, 'text 1'),
db2 (cont.) => (3, 'another text'), (5, 'sample text')@
db2 => SELECT colname, colcard, avgcollen FROM sysstat.columns
db2 (cont.) => WHERE ( tabschema, tabname ) = ( 'STOLZE', 'T' )@

COLNAME    COLCARD              AVGCOLLEN
---------- -------------------- -----------
A                            -1          -1
B                            -1          -1

  2 record(s) selected.

db2 => CALL runstats('stolze.t')@

  Return Status = 0

db2 => SELECT colname, colcard, avgcollen FROM sysstat.columns
db2 (cont.) => WHERE ( tabschema, tabname ) = ( 'STOLZE', 'T' )@

COLNAME    COLCARD              AVGCOLLEN
---------- -------------------- -----------
A                             3           4
B                             3          15

  2 record(s) selected.

db2 => SELECT colname, colcard, avgcollen FROM sysstat.columns
db2 (cont.) => WHERE ( tabschema, tabname ) = ( 'SYSIBM', 'SYSTABLES' )
db2 (cont.) => FETCH FIRST 10 ROWS ONLY@

COLNAME            COLCARD              AVGCOLLEN
------------------ -------------------- -----------
ACCESS_MODE                          -1          -1
ACTIVE_BLOCKS                        -1          -1
APPEND_MODE                          -1          -1
AST_DESC                             -1          -1
BASE_NAME                            -1          -1
BASE_SCHEMA                          -1          -1
CARD                                 -1          -1
CHECKCOUNT                           -1          -1
CHECK_DESC                           -1          -1
CHILDREN                             -1          -1

  10 record(s) selected.

db2 => CALL runstats('sysibm.systables')@
db2 => SELECT colname, colcard, avgcollen FROM sysstat.columns
db2 (cont.) => WHERE ( tabschema, tabname ) = ( 'SYSIBM', 'SYSTABLES' )
db2 (cont.) => FETCH FIRST 10 ROWS ONLY@

COLNAME            COLCARD              AVGCOLLEN
------------------ -------------------- -----------
ACCESS_MODE                           1           1
ACTIVE_BLOCKS                         2           8
APPEND_MODE                           1           1
AST_DESC                             -1          -1
BASE_NAME                             1           5
BASE_SCHEMA                           1           5
CARD                                  2           8
CHECKCOUNT                            1           2
CHECK_DESC                           -1          -1
CHILDREN                              1           3

  10 record(s) selected.

db2 => CALL runstats('unknown.table')@
SQL0443N  Routine "STOLZE.RUNSTATS" (specific name "RUNSTATS") has returned an
error SQLSTATE with diagnostic text "Error -2306 returned by db2Runstats.".
SQLSTATE=38RS1

db2 => ? sql2306@

SQL2306N The table or index "<name>" does not exist.


As you can see from the above examples, the procedure is working quite well and causes the statistics for the given tables to be updated. Additionally, the last test demonstrated that errors encountered during the processing are properly returned to the caller, and you, from the error message, can derive more information of the underlying problem. (In our case, we specified the name of a non-existing table.)


The LOAD procedure

LOAD is a very fast and direct way to get data from a file residing in the file system into a table in your database. LOAD does not use SQL INSERT statements, as does IMPORT. Instead, it writes the data directly to the data pages in the tablespace. Additionally, less logging occurs to further improve performance of the load process. (For more information on the LOAD command, see Resources.)

Due to the non-SQL nature of the command, it is not directly available in a unit of work (transaction) of the database system. Nevertheless, it is sometimes desirable to start a load process from a client application that only talks to the database server using SQL. The implementation of the stored procedure described in this section addresses those needs because CALL is a SQL statement of its own.

The procedure shown in Listing 4 takes the necessary input parameters -- the name and type of the file and the fully qualified name of the table into which the data from the file is to be loaded. It sets up the data structures required by the function db2Load and calls that function. The function call is set again in italics. Again you will notice that the preparation steps are more complex than the call itself. This is due to the many parameters that LOAD supports, and all of them need to be initialized.


Listing 4. Stored procedure code to invoke db2Load
#include <sql.h>
#include <sqludf.h>
#include <db2ApiDf.h>

int SQL_API_FN load_proc(
	SQLUDF_VARCHAR *fileName,
	SQLUDF_VARCHAR *fileType,
	SQLUDF_VARCHAR *tableName,
	SQLUDF_NULLIND *fileName_ind,
	SQLUDF_NULLIND *fileType_ind,
	SQLUDF_NULLIND *tableName_ind,
	SQLUDF_TRAIL_ARGS)
{
    SQL_API_RC rc = SQL_RC_OK;
    db2LoadStruct data;
    struct sqlu_media_list fileSources;
    struct sqlu_location_entry fileLocation;
    struct sqlca sqlca;

    data.piActionString = NULL;

    if (SQLUDF_NULL(fileName_ind) || SQLUDF_NULL(fileType_ind) ||
	    SQLUDF_NULL(tableName_ind)) {
	memcpy(SQLUDF_STATE, "38LD1", SQLUDF_SQLSTATE_LEN);
	memcpy(SQLUDF_MSGTX, "No file, file type, or table name specified.",
		SQLUDF_MSGTEXT_LEN);
	goto cleanup;
    }

    /*
     * initialize data structures
     */

    /* setup sources */
    data.piSourceList = &fileSources;
    fileSources.media_type = SQLU_SERVER_LOCATION;
    fileSources.sessions = 1;
    fileSources.target.location = &fileLocation;
    if (strlen(fileName) > SQLU_MEDIA_LOCATION_LEN) {
	memcpy(SQLUDF_STATE, "38LD2", SQLUDF_SQLSTATE_LEN);
	memcpy(SQLUDF_MSGTX, "The file name is too long.",
		SQLUDF_MSGTEXT_LEN);
	goto cleanup;
    }
    fileLocation.reserve_len = strlen(fileName);
    memcpy(fileLocation.location_entry, fileName,
	    fileLocation.reserve_len);

    /* no lob support */
    data.piLobPathList = NULL;

    /* load all columns */
    data.piDataDescriptor = NULL;

    /* action: INSERT */
    {
	struct sqlchar *action = (struct sqlchar *)malloc(
	    sizeof(struct sqlchar) + 12 + strlen(tableName));
	if (!action) {
	    memcpy(SQLUDF_STATE, "38LD3", SQLUDF_SQLSTATE_LEN);
	    memcpy(SQLUDF_MSGTX, "Memory allocation failed.",
		   SQLUDF_MSGTEXT_LEN);
	    goto cleanup;
	}
	action->length = sprintf(action->data,
		"INSERT INTO %s", tableName);
	data.piActionString = action;
    }

    /* file type is provided by the caller */
    data.piFileType = fileType;

    /* MODIFIED BY clause not supported */
    data.piFileTypeMod = NULL;

    /* all messages are discarded */
#if defined(SQLWINT)
    data.piLocalMsgFileName = "NUL";
#else /* SQLWINT */
    data.piLocalMsgFileName = "/dev/null";
#endif /* SQLWINT */

    /* system defaults are used for the remaining parameters */
    data.piTempFilesPath = NULL;
    data.piVendorSortWorkPaths = NULL;
    data.piCopyTargetList = NULL; /* no copies are created */
    data.piNullIndicators = NULL; /* no null indicators for ASC files */
    data.piLoadInfoIn = NULL;
    data.poLoadInfoOut = NULL;
    data.piPartLoadInfoIn = NULL;
    data.poPartLoadInfoOut = NULL;

   /* start the LOAD process */
   data.iCallerAction = SQLU_INITIAL;

    /* call the API */
    rc = db2Load(db2Version820, &data, &sqlca);
    if (rc != SQL_RC_OK || SQLCODE != SQL_RC_OK) {
	memcpy(SQLUDF_STATE, "38XXX", SQLUDF_SQLSTATE_LEN);
	sprintf(SQLUDF_MSGTX, "Error %d returned by db2Runstats.",
		(int)(rc == SQL_RC_OK ? SQLCODE : rc));
	goto cleanup;
    }

 cleanup:
    if (data.piActionString != NULL) {
	free(data.piActionString);
    }
    return SQLZ_DISCONNECT_PROC;
}

The procedure needs to be registered in each database where you want to use it. The CREATE PROCEDURE statement shown below (Listing 5) must be aligned with the signature of the C code function so that DB2 can pass all the values correctly through the program stack. In particular, the number and data types of the parameters, as well as null indicators and additional parameters, must be consistent with the parameter style declared in the CREATE PROCEDURE statement.


Listing 5. CREATE PROCEDURE statement
  CREATE PROCEDURE load(IN fileName VARCHAR(256),
        IN fileType VARCHAR(3), IN tableName VARCHAR(256))
     SPECIFIC load
     DYNAMIC RESULT SETS 0
     MODIFIES SQL DATA
     NOT DETERMINISTIC
     CALLED ON NULL INPUT
     LANGUAGE C
     EXTERNAL NAME 'db2_api!load_proc'
     FENCED  THREADSAFE
     NO EXTERNAL ACTION
     PARAMETER STYLE SQL@

Testing the procedure

The first step in testing the procedure is to prepare a file that can be loaded in a table. The file has to reside on the server side of the database system because the procedure will be executed on the database server and has only access to that machine. (We, therefore, run our samples directly on the server itself.) Once the file is created using DB2's EXPORT functionality, you can use it to test the procedure. This and some further steps are illustrated in Listing 6.


Listing 6. Testing the LOAD procedure
$ db2 -td@

db2 => CREATE TABLE stolze.t ( a INT NOT NULL, b VARCHAR(100), PRIMARY KEY(a) )@
db2 => INSERT INTO stolze.t VALUES (1, '2'), (3, '4'), (5, '6')@
db2 => EXPORT TO /home/stolze/testdata.ixf OF IXF SELECT * FROM stolze.t@
SQL3104N  The Export utility is beginning to export data to file
"/home/stolze/testdata.ixf".

SQL3105N  The Export utility has finished exporting "3" rows.

Number of rows exported: 3

db2 => CREATE TABLE stolze.target LIKE stolze.t@
db2 => CALL load('/home/stolze/testdata.ixf', 'IXF', 'stolze.target')@

  Return Status = 0

db2 => SELECT * FROM stolze.target@

A           B
----------- -------------------
          1 2
          3 4
          5 6

  3 record(s) selected.

db2 => CREATE TABLE stolze.t2 ( a INT NOT NULL )@
db2 => CALL load('/home/stolze/testdata.ixf', 'IXF', 'stolze.t2')@

  Return Status = 0

db2 => SELECT * FROM stolze.t2@

A
-----------
          1
          3
          5

  3 record(s) selected.

db2 => CALL load('/home/stolze/testdata.ixf', 'IXF', 'unknown.table')@
SQL0443N  Routine "STOLZE.LOAD" (specific name "LOAD") has returned an error
SQLSTATE with diagnostic text "Error -3304 returned by db2Runstats.".
SQLSTATE=38LD4
db2 => ? sql3304@

SQL3304N The table does not exist.

Explanation:

The parameters specified on the command require that the table
exists.

The tests have shown that the procedure works as expected. The LOAD is performed successfully and the data is populated into the respective tables. When using this procedure, keep the behavior of LOAD itself in mind. For example, if there are any dependent tables (tables that have a foreign key referencing the table into which data is to be loaded, for example), they might be placed in the CHECK PENDING state. Additionally, further constraints could also result in the table not being available right after the LOAD operation, requiring you to run the SET INTEGRITY SQL statement first.


Building and installing the procedures

The source code for both stored procedures presented above can be found under Download. In order to build the procedures, you might want to use the bldrtn script that comes with the C code samples of DB2 and can be found in the sqllib/samples/c/ directory of the DB2 instance.

The bldrtn script takes care of all the necessary steps. First, it compiles the C source code into an object file and subsequently creates the shared library that will be loaded by DB2 at runtime when one of the procedures is called. The library will be copied to the sqllib/function/ directory in the final step. Listing 7 demonstrates how you use the script with the db2_api.c source file, which contains the implementation for both procedures discussed above.


Listing 7. Building the procedures and verifying the installation
$ cp ~/sqllib/samples/c/bldrtn .
$ ./bldrtn db2_api
$ ls -sh ~/sqllib/function/db2_api
24K /home/stolze/sqllib/function/db2_api

Once you have your own implementation of the procedures running and tested to your satisfaction, you can apply it to your production system. Note that you do not have to install a C compiler or the DB2 samples on the production system. You merely have to copy the shared library db2_api to the target system and place it in the sqllib/function/ directory. Of course, you need to register the procedures in the databases of the target system, and you are all set up.

A final word should be said about authorization inside the procedures. From the API documentations listed under Resources, you can derive that the DB2 APIs perform their own authorization checking. You only need to be aware that the APIs are called implicitly with the authorization of the user who called the stored procedure; usually that's also the user who connected to the database.


Summary

Many of the DB2 commands are only available using the DB2 command line or a C API. In this article we demonstrated how you can easily make almost any DB2 command available for any application by wrapping it into a stored procedure. This approach allows the application to be implemented in any language, like Java or PHP. By means of example, we used the functionality of the RUNSTATS and LOAD commands, but of course the mechanism can be applied to any other command that is available with a DB2 API.



Download

DescriptionNameSizeDownload method
C source code and SQL scriptsdb2_api.zip5 KB FTP | HTTP

Information about download methods


Resources

About the author

Author photo

Knut Stolze started his work with DB2 when he joined IBM as a visiting scientist at the Silicon Valley Lab where he worked on the DB2 Image Extender. For the following two years he moved on to the DB2 Spatial Extender team and was responsible for several enhancements to improve the usability, the performance, and the standard-conformance of the Extender. Currently, he works as a teaching assistant at the University of Jena, Germany, and continues his work for IBM in the area of the Information Integrator development. He can be reached through the newsgroups comp.databases.ibm-db2 and ibm.software.db2.udb.spatial or via stolze@de.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Information Management
ArticleID=83694
ArticleTitle=Make DB2 administrative APIs available through SQL
publish-date=05192005
author1-email=stolze@de.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers