The fourth argument of a callback function is a pointer to callback user data. The user data is a C variable or structure that contains application-specific information that a callback can use. You pass the user data to a callback when you register the callback with the mi_register_callback( ) function. The fourth argument of mi_register_callback( ) provides a pointer to the user data (see Figure 57).
One of the most common uses of user data is a user-defined error structure. When a callback handles exceptions, DataBlade API functions return either MI_ERROR or NULL on failure. This information is often not specific enough for the calling code to determine the cause of the error. You can create a user-defined error structure to pass more specific error information back to the calling code, as follows:
The calling code can define a user-defined error structure to hold error information. This user-defined structure can be a single C variable or a structure with several pieces of error information. It can contain as much error information as the calling code requires.
Figure 63 shows a sample user-defined error structure named DB_ERROR_BUF.
#define MSG_SIZE 256 typedef struct error_buf_ { mi_integer error_type; mi_integer error_level; mi_string sqlstate[6]; mi_string error_msg[MSG_SIZE]; } DB_ERROR_BUF;
The DB_ERROR_BUF structure holds the following error information.
For exception handling, this event type should always be MI_Exception.
For exception handling, this field holds the exception level: MI_MESSAGE or MI_EXCEPTION.
The calling code must allocate memory for the user-defined error structure. You can use the DataBlade API memory-allocation functions such as mi_alloc( ) and mi_dalloc( ). When you allocate the user-defined error structure, you must associate a memory duration with this structure that you declare that is appropriate to its usage. For example, if the user-defined error structure is to be associated with a registered callback, you must allocate the structure with a memory duration of PER_STMT_EXEC so that this memory is still allocated when the callback executes.
The following mi_dalloc( ) call allocates a DB_ERROR_BUF buffer with a PER_STMT_EXEC memory duration:
mi_dalloc(sizeof(DB_ERROR_BUF), PER_STMT_EXEC);
The calling code can use one of the following ways to make a user-defined error structure available to a callback:
To associate a user-defined error structure with the registered callback, specify the address of the structure as the fourth argument of mi_register_callback( ) function. The call to mi_register_callback( ) initializes the fourth parameter of the exception callback with a pointer to the user-defined structure. For more information, see Figure 57.
The following func1( ) UDR registers a callback named excpt_callback2( ), which puts error information in the DB_ERROR_BUF user-defined structure (which Figure 63 defines):
void func1(flag) mi_integer flag; { MI_CONNECTION *conn; MI_CALLBACK_HANDLE *cback_hndl; DB_ERROR_BUF error; /* Initialize information in the error buffer */ error.sqlstate[0] = '\0'; strcpy(error.error_msg, "func3: initialized error buffer."); /* Obtain connection descriptor */ conn = mi_open(NULL, NULL, NULL); if ( conn == NULL ) mi_db_error_raise(NULL, MI_EXCEPTION, "func1: mi_open( ) call failed!"); /* Register the exception callback */ cback_hndl = mi_register_callback(conn, MI_Exception, excpt_callback2, (void *)&error), NULL): /* Execute SQL statement */ mi_exec(conn, "bad SQL statement", MI_QUERY_NORMAL); /* Execution does not reach this point if the * excpt_callback2( ) callback returns MI_CB_CONTINUE. */
The call to mi_register_callback( ) specifies the address of the user-defined structure as its fourth argument. This structure is, in turn, passed in as the third argument of the excpt_callback2( ) callback (see Figure 57). The following code implements the excpt_callback2( ) callback function:
MI_CALLBACK_STATUS excpt_callback2(event_type, conn, event_info, user_data) MI_EVENT_TYPE event_type; MI_CONNECTION *conn; void *event_info; void *user_data; /* user-defined error buffer gets * passed here */ { DB_ERROR_BUF *user_info; mi_integer state_type; mi_string *msg; user_info = ((DB_ERROR_BUF *)user_data); user_info->error_type = event_type; if ( event_type != MI_Exception ) { user_info->sqlstate[0] = '\0'; sprintf(user_info->error_msg, "excpt_callback2 called with wrong event type ", "%d", event_type); } else /* event_type is MI_Exception */ { mi_error_sql_state((MI_ERROR_DESC *)event_info, user_info->sqlstate, 6); mi_errmsg((MI_ERROR_DESC *)event_info, user_info->error_msg, MSG_SIZE-1); } return MI_CB_EXC_HANDLED; }
The following code fragment from a client LIBMI application registers a callback named clntexcpt_callback2( ), which puts error information in the DB_ERROR_BUF user-defined structure (which Figure 63 defines).
int main (argc, argv) int argc; char **argv; { MI_CONNECTION *conn = NULL; char stmt[300]; MI_CALLBACK_HANDLE callback_hdnl; DB_ERROR_BUF error_buff; mi_integer ret; /* Open a connection to the database server */ conn = mi_open(argv[1], NULL, NULL); if ( conn == NULL ) /* do something appropriate */ /* Register the exception callback, with the user-defined * error structure as the fourth argument to * mi_register_callback( ) */ callback_hndl = mi_register_callback(conn, MI_Exception, (MI_VOID *)clntexcpt_callback2; (MI_VOID *)&error_buff, NULL); if ( callback_hndl == NULL ) /* do something appropriate */ ... /* Execute the SQL statement that 'stmt' contains */ ret = send_statement(conn, stmt); /* If an exception occurred during the execution of the * SQL statement, the exception callback initialized the * 'error_buff' structure. Obtain error information from * 'error_buff'. */ if ( ret == MI_ERROR ) { if ( error_buff.error_type == MI_Exception ) { if ( error_buf.error_level == MI_EXCEPTION ) { fprintf(stderr, "MI_Exception: level = %d", error_buff.error_level); fprintf(stderr, "SQLSTATE='%s'\n", error_buff.sqlstate); fprintf(stderr, "message = '%s'\n", error_buff.error_msg); /* discontinue processing */ } else /* error_level is MI_WARNING */ { sprintf(warn_msg, "WARNING: %s\n", error_buf.error_msg); display_msg(warn_msg); } } /* do something appropriate */ ... }
The call to mi_register_callback( ) specifies the address of the user-defined structure as its fourth argument. This structure is, in turn, passed in as the fourth argument of the clntexcpt_callback2( ) callback. The following code implements the clntexcpt_callback2( ) callback function.
void clntexcpt_callback2(event_type, conn, event_info, error_info) MI_EVENT_TYPE event_type; MI_CONNECTION *conn; void *event_info; void *error_info; /* user-defined error buffer here */ { DB_ERROR_BUF *error_buf = (DB_ERROR_BUF *)error_info; /* Fill user-defined structure with error information */ error_buf->error_type = event_type; if ( event_type == MI_Exception ) { error_buf->error_level = mi_error_level(event_info); mi_error_sql_state(event_info, error_buf->sqlstate, 6); mi_errmsg(event_info, error_buf->error_msg, MSG_SIZE-1); } else fprintf(stderr, "Warning! clntexcpt_callback( ) fired for event ", "%d", event_type); return; }
The clntexcpt_callback( ) function is an example of an exception callback for a client LIBMI application. This callback returns void because the client LIBMI does not interpret the MI_CALLBACK_STATUS return value, as does the database server for UDRs.
To associate a user-defined error structure with the connection, you:
The following code fragment from a client LIBMI application binds the DB_ERROR_BUF user-defined structure (Figure 63) to a connection:
int main (argc, argv) int argc; char **argv; { MI_CONNECTION *conn = NULL; MI_CALLBACK_HANDLE *cback_hndl; char query[300]; mi_integer ret; DB_ERROR_BUF error_buff; conn = mi_open(argv[1], NULL, NULL); if ( conn == NULL ) /* do something appropriate */ ... cback_hndl = mi_register_callback(conn, MI_Exception, (MI_VOID)clntexcpt_callback2, NULL, NULL); ret = mi_set_connection_user_data(conn, (MI_VOID)&error_buff); if ( ret == MI_ERROR ) /* do something appropriate */ ... ret = send_command(conn, query); if ( ret == MI_ERROR) { fprintf(stderr, "MI_Exception: level = %d", error_buff.error_level); fprintf(stderr, "SQLSTATE='%s'\n", error_buff.sqlstate); fprintf(stderr, "message = '%s'\n", error_buff.error_msg); } /* do something appropriate */ ... }
The call to mi_register_callback( ) does not specify the address of the user-defined structure as its fourth argument because this structure is associated with the connection. The following code implements the clntexcpt_callback( ) callback function, which uses the mi_get_connection_user_data( ) function to obtain the user-defined structure:
void clntexcpt_callback2(event_type, conn, event_info,
user_data)
MI_EVENT_TYPE event_type;
MI_CONNECTION *conn; /* user-defined error buffer here */
void *event_info;
void *user_data;
{
DB_ERROR_BUF *error_buf
mi_get_connection_user_data(conn, (void **)&error_buf)
error_buf->error_type = event_type;
if ( event_type == MI_Exception )
{
error_buf->error_level = mi_error_level(event_info);
mi_error_sql_state(event_info, error_buf->sqlstate, 6);
mi_errmsg(event_info, error_buf->error_msg, MSG_SIZE-1);
}
else
fprintf(stderr,
"Warning! clntexcpt_callback2( ) fired for event "
"%d", event_type);
return;
}
In the preceding code fragment, the italicized portion is the only difference between this client LIBMI application and the one that registers a user-defined variable with the callback (in Associating with a Callback).