Академический Документы
Профессиональный Документы
Культура Документы
Overview
Thirteen percent (13%) of the DB2 UDB V8.1 Family Application Development exam (Exam 703) is designed to test your knowledge of ODBC and DB2 UDB's Call Level Interface and to test your ability to create a simple CLI/ODBC application. The questions that make up this portion of the exam are intended to evaluate the following: Your ability to identify the types of handles used by CLI/ODBC applications. Your ability to establish a connection to a data source within a CLI/ODBC application. Your ability to identify the correct sequence to use when calling CLI/ODBC functions. Your ability to configure the DB2 ODBC driver. Your ability to evaluate CLI/ODBC return codes and to obtain diagnostic information. Your ability to identify how cursors are created and opened by CLI/ODBC applications. This chapter is designed to introduce you to CLI/ODBC programming and to walk you through the basic steps used to construct a CLI/ODBC application. This chapter is also designed to introduce you to many of the CLI/ODBC functions commonly used in application development. Terms you will learn: Open Database Connectivity (ODBC) DB2 Call Level Interface (DB2 CLI) Application Programming Interface (API) functions SQLAllocHandle() SQLFreehandle() SQLConnect() SQLDriverConnect() SQLBrowseConnect() SQLDisconnect() SQLSetEnvAttr() SQLSetConnectAttr() SQLSetStmtAttr() SQLPrepare() SQLExecute() SQLExecDirect() SQLBindParameter() SQLBindCol() SQLFetch() SQLEndTran() Handle Environment handle SQL_HANDLE_ENV Connection handle SQL_HANDLE_DBC Statement handle SQL_HANDLE_STMT Descriptor handle SQL_HANDLE_DESC Application Parameter Descriptors (APD) Implementation Parameter Descriptors (IPD) Application Row Descriptors (ARD) Implementation Row Descriptors (IRD) Attributes SQLSTATE Connection string
Parameter markers Transaction SQL_NTS Environment attributes Connection attributes Statement attributes Return code SQL_SUCCESS SQL_SUCCESS_WITH_INFO SQL_NO_DATA (or SQL_NO_DATA_FOUND) SQL_INVALID_HANDLE SQL_NEED_DATA SQL_STILL_EXECUTING SQL_ERROR Techniques you will master: Understanding the difference between environment handles, connection handles, statement handles, and descriptor handles. Recognizing the order in which CLI/ODBC functions must be called. Knowing how to establish a data source connection from a CLI/ODBC application. Knowing how to configure the DB2 ODBC driver through environment, connection, and SQL statement attributes. Knowing how to evaluate function return codes and obtain diagnostic information. Recognizing how cursors are managed by CLI/ODBC applications.
library independently; however, when the library is used in this manner, the application will not be able to communicate with other data sources. Figure 5-1 illustrates how applications use the DB2 CLI load library in an ODBC Driver Manager environment; Figure 5-2 illustrates how applications use the DB2 CLI load library in a DB2 CLI environment.
Figure 5-1: How applications use the DB2 CLI load library in an ODBC environment.
Figure 5-2: How applications use the DB2 CLI load library in a DB2 CLI environment.
SQLFreehandle()
connection, SQL statement, or descriptor handle. Data Source Connection Management SQLConnect() SQLDriverConnect( ) Establishes a connection to a specific data source using just an authentication (user) ID and a corresponding password. Establishes a connection to a specific data source or driver using a connection string (a string that is comprised of parameters that are needed to establish a connection such as data source name, user ID, and password) OR causes a dialog that allows the end user to provide connection information such as data source name, authentication ID, and password to be displayed. (A connection is then established using the information provided.) Returns successive connection attributes and corresponding valid values to the calling application. When values have been selected for each connection attribute returned, a connection to the appropriate data source is established. Sets the current active connection in applications that have established multiple concurrent data source connections and that contain embedded SQL statements along with CLI function calls.
SQLBrowseConnect( )
Terminates a data source connection. CLI/ODBC Driver Management SQLDataSources() SQLGetInfo() SQLGetFunctions() SQLGetTypeInfo() SQLGetEnvAttr() SQLSetEnvAttr() SQLGetConnectAttr () SQLSetConnectAttr () SQLGetStmtAttr() SQLSetStmtAttr() SQL Statement Processing SQLPrepare() SQLExtendedPrepar Prepares an SQL statement for execution. Prepares an SQL statement for execution and assigns values to one Generates a list of data sources that an application can establish a connection to. Obtains information about a specific ODBC driver and its associated data source. Obtains information about whether a specific CLI/ODBC function is supported by a driver (and its associated data source). Obtains information about the native data types that are supported by a driver (and its associated data source). Obtains the current value assigned to a specific environment attribute. Assigns a value to a specific environment attribute. Obtains the current value assigned to a specific connection attribute. Assigns a value to a specific connection attribute. Obtains the current value assigned to a specific SQL statement attribute. Assigns a value to a specific SQL statement attribute.
Table 5-1: DB2 UDB CLI Functions CLI/ODBC Function e()[*] SQLBindParameter( ) SQLExtendedBind()
[*]
Purpose or more SQL statement attributes at the same time. Associates a local application variable with a parameter marker in an SQL statement. Associates an array of local application variables with parameter markers in an SQL statement. Obtains the number of parameter markers used in an SQL statement. Obtains information about a specific parameter marker in an SQL statement. Executes a prepared SQL statement. Prepares and executes an SQL statement. Retrieves the text of an SQL statement containing vendor-specific SQL extensions (referred to as vendor escape clauses) after a data source driver has translated it. Is used in conjunction with the SQLPutData() function to send long data values for data-at-execution parameters (parameters defined as SQL_DATA_AT_ EXEC) to a data source for processing. Is used in conjunction with the SQLParamData() function to send part or all of a long data value to a data source for processing. Cancels SQL statement processing. Commits or rolls back the current transaction. Ends SQL statement processing, closes any associated cursor, discards pending results and optionally, frees all resources associated with a statement handle. Returns a set of commonly-used descriptor information (column name, type, precision, scale, nullability) for a specific column in a result data set. Obtains information about the number of columns found in a result data set. Obtains information about a specific column in a result data set. Associates a local application variable with a column in a result data set. Obtains the name assigned to the cursor (if any) associated with a statement handle. Assigns a name to the cursor (if any) associated with a statement handle. Advances the cursor pointer to the next row in a result data set, retrieves a single row of data from a result data set, and copies any data retrieved to "bound" application variables.
SQLParamData()
Table 5-1: DB2 UDB CLI Functions CLI/ODBC Function SQLFetchScroll() Purpose Advances the cursor pointer, retrieves multiple rows of data (a rowset) from a result data set, and copies any data retrieved to "bound" application variables. Closes the cursor (if any) associated with a statement handle. Retrieves data from a single column (of the current row) of a result data set. Determines whether more result data sets or row count values are available, and if there are, initializes processing for them. Provides non-sequential access to multiple result data sets returned by a stored procedure. Positions a cursor within a fetched block of data and allows an application to refresh data in a rowset or update and delete data in a result data set. Performs bulk insertions and bookmark operations, including update, delete, and fetch-by-bookmark operations. Obtains information about the number of rows affected by the execution of insert, update, and delete operations. Obtains the current values of multiple fields of a descriptor record. (This information describes the name, data type, and storage sizes used by the data associated with a particular parameter marker or result data set column.) Assigns values to multiple fields of a descriptor record. Obtains the current value assigned to a field in a descriptor header or a parameter/column record. Assigns a value to a field in a descriptor header or a parameter/column record. Copies the contents of one descriptor to another. Associates a large object file with a parameter marker in an SQL statement. Associates a large object file with a column in a result data set. Obtains the length of a large object data value that is referenced by an LOB locator. Determines the starting position of a substring within a source string that is referenced by an LOB locator. Creates a new LOB locator that references a substring within a source string that is referenced by an LOB locator.
SQLSetDescRec() SQLGetDescField() SQLSetDescField() SQLCopyDesc() Large Object Support SQLBindFileToPara m()[*] SQLBindFileToCol( )[*] SQLGetLength()[*] SQLGetPosition()[*] SQLGetSubString()
[*]
Table 5-1: DB2 UDB CLI Functions CLI/ODBC Function DataLink Support SQLBuildDataLink( )[*] SQLGetDataLinkAtt r()[*] System Table Processing SQLTables() Obtains a list of catalog names, schema names, table names, and/or table types that have been defined for a particular data source. Obtains a list of table names, along with the authorization information associated with those tables that have been defined for a particular data source. Obtains a list of column names that have been defined for one or more tables. Obtains a list of column names, along with the authorization information associated with those columns, that have been defined for a particular table. Obtains a list of the optimal set of columns that uniquely identify a row of data in a particular table. Obtains a list of column names that make up the primary key of a particular table. Obtains a list of column names that make up the foreign keys (if any) of a particular table or a list of foreign keys that refer to a particular table. Obtains a list of stored procedures that have been stored in and are available for use with a data source. Obtains a list of input and output parameters associated with a stored procedure, return values for a particular stored procedure, and columns in the result data set produced by a stored procedure (if any). Obtains statistical information about a specific table and a list of associated indexes for that table. Obtains error, warning, and/or status information from a diagnostic record that was generated by the last CLI/ODBC function executed. Obtains the current value assigned to a field in a diagnostic record header or a diagnostic record that was generated by the last CLI/ODBC function executed. Obtains SQLCA data structure information that is associated with the last SQL statement executed. (Although this function is available, it has been depreciatedthe SQLGetDiagRec() and SQLGetDiagField() should be used to obtain this information Purpose Builds a DataLink value from values provided. Obtains the current value assigned to a specific attribute of a DataLink value.
SQLGetSQLCA()[*]
instead.) Adapted from Table 1 on Pages 1-7 of the DB2 Call Level Interface Guide and Reference, Volume 2
[*]
Functions available only with DB2 CLI; these functions are not supported by ODBC.
Embedded SQL applications and CLI/ODBC applications also differ in the following ways: CLI/ODBC applications do not require the explicit declaration and use of host variables; any variable can be used to send data to or retrieve data from a data source. Cursors do not have to be explicitly declared by CLI/ODBC applications. Instead, cursors are automatically generated, if needed, whenever the SQLPrepare() or SQLExecDirect() function is executed. Cursors do not have to be explicitly opened in CLI/ODBC applications; cursors are implicitly opened as soon as they are generated. CLI/ODBC functions manage environment, connection, and SQL statementrelated information using handles, which allows this information to be treated as an abstract object. The use of handles eliminates the need for CLI/ODBC applications to use database product-specific data structures such as DB2 UDB's SQLCA, SQLCHAR, and SQLDA data structures. Unlike embedded SQL, CLI/ODBC applications inherently have the ability to establish multiple connections to multiple data sources or to the same data source at the same time. (Embedded SQL applications can only connect to multiple data sources at the same time if Type 2 connections are used.) Despite these differences, one important common concept exists between embedded SQL applications and CLI/ODBC applications: CLI/ODBC applications can execute any SQL statement (using a CLI/ODBC function) that can be dynamically prepared in an embedded SQL application. This is guaranteed because CLI/ODBC applications pass all of their SQL statements directly to the data source for dynamic execution. (CLI/ODBC applications can also execute some SQL statements that cannot be dynamically prepared, such as compound SQL statements, but for the most part, static SQL is not supported.) By allowing the data source to process all SQL statements submitted by a CLI/ODBC application, the portability of CLI/ODBC applications is guaranteed. This is not always the case with embedded SQL applications because the way SQL statements are dynamically prepared can vary with each relational database product used. Also, because COMMIT and ROLLBACK SQL statements can be dynamically prepared by some database products (including DB2 UDB) but not by others, they are typically not used in CLI/ODBC applications. Instead, CLI/ODBC applications rely on the SQLEndTran() function to terminate active transactions (when manual-commit is used). This ensures that CLI/ODBC applications can successfully end transactions, regardless of which database product is being used.
tasks must be called in a specific order or an error will occur. Figure 5-3 illustrates some of the basic CLI/ODBC functions used to perform initialization and termination.
Figure 5-3: CLI/ODBC application initialization and termination. Aside from performing these three basic tasks, CLI/ODBC applications can also perform additional tasks like error handling and message processing. (We will look at how errors are handled in a CLI/ODBC application a little later.)
Initialization
During initialization, resources that will be needed by the transaction processing task are allocated (and initialized), and connections to any data source(s) that the transaction processing task will interact with are established. Three basic steps are performed as part of the initialization process. They are: Resource allocation Application ODBC version declaration Data source connection management
Resource Allocation
The resources used by CLI/ODBC applications consist of special data storage areas that are identified by unique handles. A handle is simply a pointer variable that refers to a data object controlled by DB2 CLI or the ODBC Driver Manager and referenced by CLI/ODBC function calls. By using data storage areas and handles, CLI/ODBC applications are freed from the responsibility of having to allocate and manage global variables and/or data structures like the SQLCA and SQLDA data structures used with embedded SQL applications. Four types of handles are available: Environment handles Connection handles Statement handles Descriptor handles Environment handle. An environment handle is a pointer to a data storage area that contains CLI/ODBC- specific information that is global in nature. This information includes: The current state of the environment. The current value of each environment attribute available. A handle for each connection data storage area currently associated with the environment handle. The number of connections currently available in the environment, along with their current states (i.e., "Connected" or "Disconnected"). Diagnostic information about the current environment. Every CLI application must begin by allocating an environment handle. Usually, only one environment handle is allocated per application, and that handle must exist before any other handles can be allocated; all other handles are managed within the context of the environment handle used. Environment handles are allocated by calling the SQLAllocHandle() function with the SQL_HANDLE_ENV option specified. Thus, the source code used to allocate an environment handle looks something like this : SQLHANDLE EnvHandle = 0; SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle); Connection handle. A connection handle is a pointer to a data storage area that contains information about a data source (database) connection being managed by CLI/ODBC. This information includes: The current state of the connection being managed. The current value of each connection attribute available. A handle for every SQL statement data storage area associated with the connection handle. A handle for every descriptor data storage area associated with the connection handle. Diagnostic information about the connection being managed. All connections to data sources are made via connection handles; therefore, a connection handle must exist before a connection to any data source can be established. Connection handles are allocated by calling the SQLAllocHandle() function with the SQL_HANDLE_DBC option and a valid environment handle specified. The source code used to allocate a connection handle looks something like this: SQLHANDLE ConHandle = 0; if (EnvHandle != 0) SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConHandle); Statement handle. A statement handle is a pointer to a data storage area that contains specific information about a single SQL statement. This information includes: The current state of the associated SQL statement. The current value of each SQL statement attribute available.
The addresses of all application variables that have been bound to (associated with) any parameter markers used by the SQL statement. The addresses of all application variables that have been bound to (associated with) the columns of any result data set produced by the SQL statement. Diagnostic information about the SQL statement.
The statement handle is the real workhorse of CLI/ODBC. Statement handles are used to process each SQL statement contained in an application (both user-defined SQL statements and SQL statements that are performed behind-the-scenes when certain CLI/ODBC functions are called). Notably, statement handles are used to bind application variables to parameter markers used in a statement, prepare and submit a statement to the appropriate data source for execution, obtain metadata about any result data set(s) produced in response to a statement, bind application variables to columns found in any result data set(s) produced, retrieve (fetch) data from any result data set(s) produced, and obtain diagnostic information when a statement fails to execute. Each SQL statement used in an application must have its own statement handle, and each statement handle used can be associated with only one connection handle. However, any number of statement handles can be associated with a single connection handle. Statement handles are allocated by calling the SQLAllocHandle() function with the SQL_HANDLE_STMT option and a valid connection handle specified. Thus, the source code used to allocate a statement handle looks something like this: SQLHANDLE StmtHandle = 0; if (ConHandle != 0) SQLAllocHandle(SQL_HANDLE_STMT, ConHandle, &StmtHandle); Descriptor handle. A descriptor handle is a pointer to a data storage area that contains a collection of metadata that describes either the application variables that have been bound to parameter markers in an SQL statement or the application variables that have been bound to the columns of a result data set produced in response to a query. Four types of descriptors are recognized by CLI/ODBC: Application Parameter Descriptors (APD). Contain information about the application variables (buffers) that have been bound to parameter markers used in an SQL statement such as their addresses, lengths, and high-level programming language data types. Implementation Parameter Descriptors (IPD). Contain information about the parameters used in an SQL statement such as their SQL data types, lengths, and nullability characteristics. Application Row Descriptors (ARD). Contain information about the application variables (buffers) bound to the columns of a result data set such as their addresses, lengths, and highlevel programming language data types. Implementation Row Descriptors (IRD). Contain information about the columns in a result data set such as their SQL data types, lengths, and nullability characteristics. Four descriptor handles (one of each descriptor type) are automatically allocated and associated with a statement handle as part of the statement handle allocation process. Once allocated, these descriptor handles remain associated with the corresponding statement handle until it is destroyed (at which time the descriptor handles will also be destroyed). Descriptor handles can also be explicitly allocated and associated with a statement handle to fulfill the role of an APD or ARD descriptor that was implicitly allocated. Most CLI/ODBC operations can be performed using the descriptor handles that are implicitly defined. However, when used, explicitly-defined descriptor handles can provide a convenient shortcut for performing some operations.
Handle management. As we have already seen, handles are initialized and their corresponding data storage areas are allocated by calling the SQLAllocHandle() function with the appropriate handle type and parent handle specified. (Handle types include SQL_HANDLE_ENV [environment], SQL_HANDLE_DBC [connection], SQL_HANDLE_STMT [SQL statement], and SQL_HANDLE_DESC [descriptor].) Once created, handles remain available for use by other CLI/ODBC functions until they are explicitly freed, either during transaction processing or as part of the termination task.
pairs, separated by semicolons, that contains information to be used to establish a connection to a data source. Some of the more common keyword/value pairs used are identified in Table 5-2. Table 5-2: Common Keyword/Value Pairs Used to Construct Connection Strings Keyword/Value DSN=DataSourceName Purpose Specifies the name of a data source (as returned by the SQLDataSources() function) that a connection is to be established with. Specifies the user ID (authorization ID) of the user attempting to establish the connection. Specifies the password corresponding to the user ID (authorization ID) specified. If a password is not required for the specified user ID, an empty password string should be used (PWD=;). Specifies the new password that is to be assigned to the user ID (authorization ID) specified. If the NEW-PWD keyword is used but no new password is provided (NEWPWD=;), the DB2 CLI driver will prompt the user to provide a new password.
UID=UserID PWD=Password
NEWPWD=NewPassword
Thus, an application that always connects to a database named "PAYROLL" using the authorization ID "db2admin" and the corresponding password "ibmdb2" could use a connection string with the SQLDriverConnect() function that looks something like this: DSN=PAYROLL;UID=db2admin;PWD=ibmdb2; When invoked, the SQLDriverConnect() function parses the connection string provided and, using the data source name specified, attempts to retrieve additional information needed from the system (for example, the server's IP address) to establish a connection. Using this information, the function then logs on to the appropriate server and attempts to connect to the designated data source. Applications using the SQLDriverConnect() function can also let the driver prompt the user for any connection information needed. For example, when the SQLDriverConnect() function is called with an empty connection string, DB2 CLI will display the dialog shown in Figure 5-4.
Figure 5-4: The DB2 UDB SQLDriverConnect() user input dialog. This dialog prompts the user to select a data source from a list of data sources recognized by DB2 CLI and to provide a user ID along with a corresponding password. Once this information has been provided, an appropriate connection string is constructed and that string is used to establish a connection to the appropriate data source. Whether this dialog will be displayed is determined by one of the parameter values passed to the SQLDriverConnect() function: if this function is called with the SQL_DRIVER_PROMPT, SQL_DRIVER_COMPLETE, or SQL_DRIVER_COMPLETE_REQUIRED option specified, the dialog will be displayed if the connection string provided does not contain enough information to establish a data source connection. On the other hand, if this function is called with the SQL_DRIVER_NOPROMPT option specified and more information is needed, an error will be generated. The SQLBrowseConnect() function. Like the SQLDriverConnect() function, the SQLBrowseConnect() function uses a connection string to send connection information to a driver. However, unlike the SQLDriverConnect() function, the SQLBrowseConnect() function can be used to construct a complete connection string at application run time. This difference allows an application to: Use its own dialog to prompt users for connection information, thereby retaining control over its "look and feel." Search (browse) the system for data sources that can be used by a particular driver, possibly in several steps. For example, an application might first browse the network for servers and after choosing a server, it can then browse the server for databases that can be accessed by a specific driver. Regardless of which function is used, when a connection is made to a data source by way of a connection handle, the driver that is used to communicate with the corresponding data source is loaded into memory, an array of pointers to the CLI/ODBC functions that are supported by that driver are stored in the data storage area that the connection handle points to, and the data
source itself is made available to the application that established the connection. At that point, the connection handle is said to be in an "Active" or "Connected" state (prior to that point, the connection handle was in an "Allocated" state). There-after, each time a CLI/ODBC function is called with the connection handle specified, the corresponding function entry point is located in the array of function pointers associated with the connection handle and the call is routed to the appropriate function within the driver, where it is executed.
Transaction Processing
The transaction processing task follows initialization and makes up the bulk of a CLI/ODBC application. This is where the SQL statements that query and/or manipulate data are passed to the appropriate data source (which in our case is typically a DB2 UDB database) by various CLI/ODBC function calls for processing. In the transaction processing task, an application performs the following five steps (in the order shown): 1. Allocates one or more statement handles 2. Prepares and executes one or more SQL statements 3. Retrieves and processes any results produced 4. Terminates the current transaction by committing it or rolling it back 5. Frees all statement handles allocated Figure 5-5 illustrates the basic steps that are performed during the transaction processing task and identifies the CLI/ODBC function calls that are typically used to execute each step.
// Prepare The SQL Statement RetCode = SQLPrepare(StmtHandle, SQLStmt, SQL_NTS); // Bind The Parameter Marker Used In The SQL Statement To // An Application Variable RetCode = SQLBindParameter(StmtHandle, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(JobType), 0, JobType, sizeof(JobType), NULL); // Populate The "Bound" Application Variable strcpy((char *) JobType, "DESIGNER"); // Execute The SQL Statement RetCode = SQLExecute(StmtHandle); ...
statement parameter markers. In this case, application variables are used as output arguments rather than as input, and data is retrieved and written directly to the arguments whenever the SQLFetch() function is called. However, because the SQLGetData() function can also be used to retrieve data from a result data set, application variable/column binding is optional. In the third step, data stored in the result data set is retrieved by repeatedly calling the SQLFetch() function (usually in a loop) until data is no longer available. If application variables have been bound to columns in the result data set, their values are automatically updated each time SQLFetch() is called. On the other hand, if column binding was not performed, the SQLGetData() function can be used to copy data from a specific column to an appropriate application variable. The SQLGetData() function can also be used to retrieve large variable length column data values in several small pieces (which cannot be done when bound application variables are used). All data stored in a result data set can be retrieved by using any combination of these two methods; if data conversion is necessary, it will take place automatically when the SQLFetch() function is calledprovided bound variables are used. (The appropriate conversion to use can be specified as input to the SQLGetData() function.) The following pseudo-source code example, written in the C programming language, illustrates how application variables can be bound to the columns in a result data set, and how data in a result data set is normally retrieved by repeatedly calling the SQLFetch() function: ... // Bind The Columns In The Result Data Set Returned // To Application Variables SQLBindCol(StmtHandle, 1, SQL_C_CHAR, (SQLPOINTER) EmpNo, sizeof(EmpNo), NULL); SQLBindCol(StmtHandle, 2, SQL_C_CHAR, (SQLPOINTER) LastName, sizeof(LastName), NULL); // While There Are Records In The Result Data Set // Produced, Retrieve And Display Them while (RetCode != SQL_NO_DATA) { RetCode = SQLFetch(StmtHandle); if (RetCode != SQL_NO_DATA) printf("%-8s %s\n", EmpNo, LastName); } ... It is important to note that, unlike embedded SQL, in which cursor names are used to retrieve, update, or delete rows stored in a result data set, cursor names for CLI/ODBC are needed only for positioned update and positioned delete operations (because they reference cursors by name). Thus, cursor names are automatically generated, if appropriate, when a result data set is produced; however, they are seldom used explicitly.
points of data consistency within a database; the effects of all operations performed within a transaction are either applied to the database and made permanent (committed) or backed out (rolled back) and the database is returned to the state it was in before the transaction was initiated. CLI/ODBC applications can connect to multiple data sources simultaneously, and each data source connection can constitute a separate transaction boundary. Figure 5-6 shows how multiple transaction boundaries can coexist when a CLI/ODBC application interacts with two separate data sources at the same time.
Figure 5-6: Transaction boundaries in an application that interacts with multiple data sources simultaneously. From a transaction processing viewpoint, a CLI/ODBC application can be configured such that it runs in one of two modes: auto-commit or manualcommit. When auto-commit mode is used, each individual SQL statement is treated as a complete transaction, and each transaction is automatically committed after the SQL statement successfully executes. For anything other than SELECT SQL statements, the commit operation takes place immediately after the statement is executed. For SELECT statements, the commit operation takes place immediately after the cursor being used to process any result data set produced is closed. (Remember that CLI/ODBC automatically declares and opens a cursor if one is needed.) Auto-commit mode is the default commit mode used and is usually sufficient for simple CLI/ODBC applications. Larger applications, howeverparticularly applications that perform update operationsshould switch
to manual-commit mode (by calling the SQLSetConnectAttr() function with the SQL_ATTR_ AUTOCOMMIT option specified) as soon as a data source connection is established. When manual-commit mode is used, transactions are started implicitly the first time an application accesses a data source, and transactions are explicitly ended when the SQLEndTran() function is called. This CLI/ODBC function is used to either roll back or commit all changes made by the current transaction. Thus, all operations performed against a data source between the time it was first accessed and the time the SQLEndTran() function is called are treated as a single transaction. Regardless of which type of commit mode is used, all transactions associated with a particular data source should be completed before the connection to that data source is terminated. However, you should not wait until you are about to terminate a data source connection before you decide to end a transactiondoing so can cause concurrency and locking problems among other applications to occur. Likewise, it is not always a good idea to use auto-commit mode or to call the SQLEndTran() function after each SQL statement is executedthis behavior increases overhead and can lead to poor application performance. When deciding on when to end a transaction, consider the following: Only the current transaction can be committed or rolled back; therefore, all dependent SQL statements should be kept within the same transaction. Various table-level and row-level locks may be held by the current transaction. When the transaction is ended, these locks are released and other applications are given access to all data that was locked. Once a transaction has been successfully committed or rolled back, it is fully recoverable from the transaction log files. Any transaction that has not been committed or rolled back at the time a system failure or application program failure occurs will be "lost" and its effects will be discarded. Therefore, transactions should be ended as soon as reasonably possible. Note DB2 UDB guarantees that successfully committed or rolled-back transactions are fully recoverable from the system log files; however, this might not be true for other database products. If a CLI/ODBC application is designed to interact with multiple database products, you should refer to the appropriate product documentation to determine when transactions are considered recoverable. When defining transaction boundaries, keep in mind that all resources associated with the transactionwith the exception of those coupled with a held cursorare released. Prepared SQL statements, cursor names, parameter bindings, and column bindings, however, are maintained from one transaction to the next. This means that once an SQL statement has been prepared, that statement does not need to be prepared againeven after a commit or roll back operation is performedprovided it remains associated with the same statement handle. Also, by default, cursors are preserved after a transaction is committed and are closed when a transaction is rolled back.
Note
If a statement handle is not freed, it can be used to process other SQL statements. However, when such a statement handle is reused, any access plans that had been cached for the previous SQL statement associated with that handle will be discarded.
Termination
As you might imagine, the termination task takes place at the end of a CLI/ODBC application, just before control is returned to the operating system. This is where all data source connections that have been established are terminated and where all resources that were allocated during initialization are freed. (Usually, these resources consist of an environment data storage area and one or more connection data storage areas.) Existing database connections are terminated by calling the SQLDisconnect() function with the appropriate connection handle specified; corresponding connection data storage areas are freed by calling the SQLFreeHandle() function with the SQL_HANDLE_DBC option and the appropriate connection handle specified. Once all previously allocated connection data storage areas have been freed, the environment data storage area that the connection data storage areas were associated with is also freed by calling the SQLFreeHandle() function, this time with the SQL_HANDLE_ENV option and the appropriate environment handle specified.
// Allocate An Environment Handle SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle); // Set The ODBC Application Version To 3.x if (EnvHandle != 0) SQLSetEnvAttr(EnvHandle, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER); // Allocate A Connection Handle if (EnvHandle != 0) SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConHandle); // Connect To The Appropriate Data Source if (ConHandle != 0) RetCode = SQLConnect(ConHandle, (SQLCHAR *) "SAMPLE", SQL_NTS, (SQLCHAR *) "db2admin", SQL_NTS, (SQLCHAR *) "ibmdb2", SQL_NTS); /*------------------------------------------------------*/ /* TRANSACTION PROCESSING */ /*------------------------------------------------------*/ // Allocate An SQL Statement Handle if (ConHandle != 0 && RetCode == SQL_SUCCESS) SQLAllocHandle(SQL_HANDLE_STMT, ConHandle, &StmtHandle); // Define A SELECT SQL Statement That Uses A Parameter // Marker strcpy((char *) SQLStmt, "SELECT EMPNO, LASTNAME FROM "); strcat((char *) SQLStmt, "EMPLOYEE WHERE JOB = ?"); // Prepare The SQL Statement RetCode = SQLPrepare(StmtHandle, SQLStmt, SQL_NTS); // Bind The Parameter Marker Used In The SQL Statement To // An Application Variable RetCode = SQLBindParameter(StmtHandle, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
sizeof(JobType), 0, JobType, sizeof(JobType), NULL); // Populate The "Bound" Application Variable strcpy((char *) JobType, "DESIGNER"); // Execute The SQL Statement RetCode = SQLExecute(StmtHandle); // If The SQL Statement Executed Successfully, Retrieve // The Results if (RetCode == SQL_SUCCESS) { // Bind The Columns In The Result Data Set Returned // To Application Variables SQLBindCol(StmtHandle, 1, SQL_C_CHAR, (SQLPOINTER) EmpNo, sizeof(EmpNo), NULL); SQLBindCol(StmtHandle, 2, SQL_C_CHAR, (SQLPOINTER) LastName, sizeof(LastName), NULL); // While There Are Records In The Result Data Set // Produced, Retrieve And Display Them while (RetCode != SQL_NO_DATA) { RetCode = SQLFetch(StmtHandle); if (RetCode != SQL_NO_DATA) printf("%-8s %s\n", EmpNo, LastName); } } // Commit The Transaction RetCode = SQLEndTran(SQL_HANDLE_DBC, ConHandle, SQL_COMMIT); // Free The SQL Statement Handle if (StmtHandle != 0) SQLFreeHandle(SQL_HANDLE_STMT, StmtHandle); /*------------------------------------------------------*/ /* TERMINATION */ /*------------------------------------------------------*/
// Terminate The Data Source Connection if (ConHandle != 0) RetCode = SQLDisconnect(ConHandle); // Free The Connection Handle if (ConHandle != 0) SQLFreeHandle(SQL_HANDLE_DBC, ConHandle); // Free The Environment Handle if (EnvHandle != 0) SQLFreeHandle(SQL_HANDLE_ENV, EnvHandle);
// Return Control To The Operating System return(0); } You may have noticed that a special code named SQL_NTS was passed as a parameter value for some of the CLI/ODBC functions used in this application. CLI/ODBC functions that accept character string values as arguments usually require the length of the character string to be provided as well. The value SQL_NTS can be used in place of an actual length value to indicate that the corresponding string is null-terminated.
Environment Attributes
Environment attributes affect the way in which CLI/ODBC functions that operate under a specified environment behave. An application can retrieve the value of an environment attribute at any time by calling the SQLGetEnvAttr() function and can change the value of an environment attribute by calling the SQLSetEnvAttr() function. Some of the more common environment attributes include: SQL_ATTR_ODBC_VERSION. Determines whether certain functionality exhibits ODBC 2.0 behavior or ODBC 3.x behavior. SQL_ATTR_OUTPUT_NTS. Determines whether the driver is to append a null-terminator to string data values before they are returned to an application. It is important to note that environment attributes can be changed as long as no connection handles have been allocated against the environment. Once a connection handle has been allocated, attribute values for that environment can be retrieved but cannot be altered.
Connection Attributes
Connection attributes affect the way in which connections to data sources and drivers behave. An application can retrieve the value of a connection attribute at any time by calling the SQLGetConnectAttr() function and can change the value of a connection attribute by calling the SQLSetConnectAttr() function. Some of the more common connection attributes include: SQL_ATTR_AUTOCOMMIT. Determines whether the data source/driver will operate in autocommit mode or manual-commit mode. SQL_ATTR_MAXCONN. Determines the maximum number of concurrent data source connections that an application can have open at one time. (The default value for this attribute is 0, which means that an application can have any number of connections open at one time.) SQL_ATTR_TXN_ISOLATION. Specifies the isolation level to use for the current connection (Valid values for this attribute include SQL_TXN_SERIALIZABLE [Repeatable Read], SQL_TXN_REPEATABLE_READ [Read Stability], SQL_TXN_READ_COMMITTED [Cursor Stability], and SQL_TXN_READ_UNCOMMITTED [Uncommitted Read]). As with environment attributes, timing becomes a very important element when setting connection attributes. Some connection attributes can be set at any time; some can be set only after a corresponding connection handle has been allocated but before a connection to a data source is established; some can be set only after a connection to a data source is established; and finally, some can be set only after a connection to a data source is established and while no outstanding transactions or open cursors are associated with the connection.
at any time but are not applied until the SQL statement associated with the statement handle is used again, and some can be set at any time. Note A complete listing of the environment, connection, and SQL statement attributes available can be found in the IBM DB2 Universal Database, Version 8 CLI Guide and Reference Volume 2 product documentation.
SQL_SUCCESS_WITH_INFO
SQL_NO_DATA or SQL_NO_DATA_FOUND
SQL_INVALID_HANDLE
SQL_NEED_DATA
SQL_STILL_EXECUTING
Table 5-3: CLI/ODBC Function Return Codes Return Code Meaning SQLGetDiagField() can be used to obtain additional information.) SQL_ERROR The CLI/ODBC function failed. (The CLI/ODBC functions SQLGetDiagRec() and SQLGetDiagField() can be used to obtain additional information.)
The return code SQL_INVALID_HANDLE always indicates a programming error and should never be encountered during application run time. All other return codes provide run-time information about the success or failure of the CLI/ODBC function called (although the SQL_ERROR return code can sometimes indicate a programming error as well). Error handling is an important part of any application, and CLI/ODBC applications are no exception. At a minimum, a CLI/ODBC application should always check to see if a called CLI/ODBC function executed successfully by examining the return code produced; when such a function fails to execute as expected, users should be notified that an error or warning condition has occurred and, whenever possible, they should be provided with sufficient diagnostic information to locate and correct the problem.
SQLSTATE Values
Although a return code will notify an application program if an error or warning condition occurred, it does not provide the application (or the developer or user) with specific information about what caused the error or warning condition to be generated. Because additional information about an error or warning condition is usually needed to resolve a problem, DB2 (as well as other relational database products) uses a set of error message codes known as SQLSTATEs to provide supplementary diagnostic information for warnings and errors. SQLSTATEs are alphanumeric strings that are five characters (bytes) in length and have the format ccsss, where cc indicates the error message class and sss indicates the error message subclass. An SQLSTATE that has a class of 01 corresponds to a warning; an SQLSTATE that has a class of HY corresponds to an error that was generated by DB2 CLI; and an SQLSTATE that has a class of IM corresponds to an error that was generated by the ODBC Driver Manager. (Because different database servers often have different diagnostic message codes, SQLSTATEs follow standards that are outlined in the X/Open CLI standard specification. This standardization of SQLSTATE values enables application developers to process errors and warnings consistently across different relational database products.) Unlike return codes, SQLSTATEs are often treated as guidelines, and drivers are not required to return them. Thus, while drivers should always return the proper SQLSTATE for any error or warning they are capable of detecting, applications should not count on this always happening. Because SQLSTATEs are not returned reliably, most applications simply display them, along with any corresponding diagnostic message and native error code, to the user. Loss of functionality rarely occurs with this approach because applications normally cannot base programming logic on SQLSTATEs anyway. For example, suppose an application calls the SQLExecDirect() function and the SQLSTATE 42000 (syntax error or access violation) is returned. If the SQL statement that caused this error to occur is hard-coded into the application or constructed at application run time, the error can be attributed to a programming error and the source code will have to be modified. However, if the SQL statement that caused this error to occur was provided by the user at run time, the error can be attributed to a user mistake, in which case the application has already done all that it can do by informing the user of the problem.
SQLSMALLINT Counter = 0; SQLINTEGER NumRecords = 0; SQLINTEGER NativeErr = 0; SQLCHAR SQLState[6]; SQLCHAR ErrMsg[255]; SQLSMALLINT ErrMsgLen = 0; // Allocate An Environment Handle SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle); // Set The ODBC Application Version To 3.x if (EnvHandle != 0) SQLSetEnvAttr(EnvHandle, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER); // Allocate A Connection Handle if (EnvHandle != 0) SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConHandle); // Attempt To Connect To A Data Source Using An Invalid // User ID (This Will Cause An Error To Be Generated) if (ConHandle != 0) RetCode = SQLConnect(ConHandle, (SQLCHAR *) "SAMPLE", SQL_NTS, (SQLCHAR *) "db2_admin", SQL_NTS, (SQLCHAR *) "ibmdb2", SQL_NTS); // If Unable To Establish A Data Source Connection, // Obtain Any Diagnostic Information Available if (RetCode != SQL_SUCCESS) { // Find Out How Many Diagnostic Records Are // Available SQLGetDiagField(SQL_HANDLE_DBC, ConHandle, 0, SQL_DIAG_NUMBER, &NumRecords, SQL_IS_INTEGER, NULL); // Retrieve And Display The Diagnostic Information // Produced for (Counter = 1; Counter <= NumRecords; Counter++)
{ // Retrieve The Information Stored In Each // Diagnostic Record Generated SQLGetDiagRec(SQL_HANDLE_DBC, ConHandle, Counter, SQLState, &NativeErr, ErrMsg, sizeof(ErrMsg), &ErrMsgLen); // Display The Information Retrieved printf("SQLSTATE : %s\n", SQLState); printf("%s\n", ErrMsg); } } // Free The Connection Handle if (ConHandle != 0) SQLFreeHandle(SQL_HANDLE_DBC, ConHandle); // Free The Environment Handle if (EnvHandle != 0) SQLFreeHandle(SQL_HANDLE_ENV, EnvHandle); // Return Control To The Operating System return(0); } The important thing to remember is that more than one diagnostic record may be generated when an error or warning condition occurs. One way to ensure that all diagnostic records generated are retrieved is to examine the diagnostic header record to determine how many records are available. Another way is to repeatedly call the SQLGetDiagRec() function, incrementing the record ID number each time, until the return code SQL_NO_DATA is returned. At that point, no more diagnostic records will be available.
Figure 5-7: Converting a source code file containing CLI/ODBC functions into an executable application. It is important to note that DB2 UDB is packaged with a set of special bind files that are used to support DB2 CLI. When a database is created, these files are bound to the database as part of the database creation process to produce a package that facilitates CLI interaction with that database.
Practice Questions
Question 1 Which two of the following must be allocated before a statement handle can be allocated? A. An application handle B. An environment handle C. A data source handle D. A driver handle E. A connection handle Which of the following CLI/ODBC functions can NOT be used to obtain diagnostic information when an SQL-related error occurs? A. SQLGetError() B. SQLGetSQLCA() C. SQLGetDiagRec() D. SQLGetDiagField() The CLI/ODBC function SQL DriverConnect() is to be used to establish a connection to a DB2 UDB database. Which of the following is NOT a valid keyword that can be included in the connection string used by the SQL DriverConnect() function? A. DSN B. PWD
Question 2
Question 3
Question 4
Question 5
Question 6
Question 7
Question 8
C. UID D. ALIAS Which of the following return codes will be returned by SQL Fetch() when there are no more records to be retrieved in a result data set? A. SQL_ERROR B. SQL_NO_DATA C. SQL_SUCCESS_WITH_INFO D. SQL_NO_MORE_DATA Which of the following attributes is used to indicate that queries are to be executed using Read Stability locking semantics? A. SQL_ATTR_REPEATABLE_READ B. SQL_ATTR_CONCURRENCY C. SQL_ATTR_TXN_ISOLATION D. SQL_ATTR_CURSOR_ISOLATION Which two of the following CLI/ODBC functions can be used to establish a new connection to a remote DB2 UDB database? A. SQLDatasourceConnect() B. SQLDriverConnect() C. SQLSetConnection() D. SQLDatabaseConnect() E. SQLBrowseConnect() Which of the following is NOT a valid CLI/ODBC handle definition? A. SQL_HANDLE_ENV B. SQL_HANDLE_DBC C. SQL_HANDLE_DESC D. SQL_HANDLE_CON Given the following source code: SQLHANDLE EnvHandle, ConHandle, StmtHandle; SQLRETURN RetCode;
Question 9
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle); SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConHandle); SQLConnect(ConHandle, "SAMPLE", SQL_NTS, "db2admin", SQL_NTS, "ibmdb2", SQL_NTS); Assuming everything has executed successfully up to this point, what value will be assigned to the variable RetCode if the following line is executed next? RetCode = SQLExecDirect(ConHandle, "SELECT * FROM EMPLOYEES", SQL_NTS); A. SQL_SUCCESS B. SQL_ERROR C. SQL_INVALID_HANDLE D. SQL_NEED_DATA Which of the following will return values that are only in upper case? A. SELECT name FROM employee WHERE UCASE(name) = smith
Question 10
Question 11
B. SELECT name FROM employee WHERE UCASE(name) = SMITH C. SELECT UCASE(name) FROM employee WHERE name = smith D. SELECT name FROM employee WHERE name IN (SELECT name FROM employee WHERE UCASE(name) = UCASE(smith)) Which of the following statement attributes is used to indicate that a cursor is to be a key-set driven cursor? A. SQL_ATTR_CURSOR_TYPE B. SQL_ATTR_CURSOR_SENSITIVITY C. SQL_ATTR_CONCURRENCY D. SQL_ATTR_CURSOR_MODE Given the following table: TAB1 ----------------------EMPID NAME ------- ------1 USER1 2 USER2 3 USER3 If a CLI/ODBC application containing the following pseudo-code executes successfully: SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle); SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConHandle); SQLConnect(ConHandle, (SQLCHAR *) "SAMPLE", SQL_NTS, (SQLCHAR *) "db2admin", SQL_NTS, (SQLCHAR *) "ibmdb2", SQL_NTS); SQLSetConnectAttr(ConHandle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER); SQLAllocHandle(SQL_HANDLE_STMT, ConHandle, &StmtHandle); SQLExecDirect(StmtHandle, "INSERT INTO tab1 VALUES (4, USER4)", SQL_NTS); SQLExecDirect(StmtHandle, "INSERT INTO tab1 VALUES (5, USER5)", SQL_NTS); SQLEndTran(SQL_HANDLE_DBC, Conhandle, SQL_COMMIT); SQLExecDirect(StmtHandle, "INSERT INTO tab1 VALUES (6, USER6)", SQL_NTS); SQLExecDirect(StmtHandle, "DELETE FROM tab1 WHERE empid = 1", SQL_NTS); SQLExecDirect(StmtHandle, "DELETE FROM tab1 WHERE empid = 2", SQL_NTS); SQLEndTran(SQL_HANDLE_DBC, Conhandle, SQL_ROLLBACK); How many records will be stored in TAB1? A. 3 B. 4 C. 5 D. 6
Question 12
Question 13
Question 14
Question 15
Question 16
Question 17
Which of the following CLI/ODBC functions can cause a cursor to be opened? A. SQLFetch() B. SQLPrep() C. SQLExecDirect() D. SQLOpen() Which of the following CLI/ODBC function calls can be used to determine whether or not the results of a query are read-only? A. SQLFetch(SQL_FETCH_MODE) B. SQLGetStmtAttr(SQL_ATTR_CONCURREN CY) C. SQLGetInfo(SQL_ATTR_CURSOR_TYPE) D. SQLPrepare(SQL_READ_ONLY) When the following CLI/ODBC function is executed, the return code SQL_ERROR is produced: RetCode = SQLPrepare(Handle, "SELECT * FROM EMPLOYEES", SQL_NTS); Which of the following CLI/ODBC function calls can be used to determine why the SQLPrepare() function failed? A. SQLGetDiagRec(SQL_HANDLE_ENV, Handle, ) B. SQLGetDiagRec(SQL_HANDLE_DBC, Handle, ) C. SQLGetDiagRec(SQL_HANDLE_STMT, Handle, ) D. SQLGetDiagRec(SQL_HANDLE_DESC, Handle, ) Which of the following CLI/ODBC functions would be used to update multiple rows in a DB2 UDB table? A. SQLUpdate() B. SQLFetch() C. SQLExecute() D. SQLBulkUpdate() Which of the following handle types contains information about the application variables that have been bound to parameter markers in an SQL statement? A. Environment B. Connection C. SQL statement D. Descriptor Which two of the following options will cause a dialog to be displayed when more information is needed by the SQL DriverConnect() function than was provided in the connection string used? A. SQL_DRIVER_NOPROMPT B. SQL_DRIVER_COMPLETE
Question 18
C. SQL_DRIVER_QUERY_USER D. SQL_DRIVER_COMPLETE_REQUIRED E. SQL_DRIVER_PROMPT_USER Given the following CLI/ODBC application pseudocode that executes successfully: SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle); SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConHandle); SQLConnect(ConHandle, (SQLCHAR *) "SAMPLE", SQL_NTS, (SQLCHAR *) "db2admin", SQL_NTS, (SQLCHAR *) "ibmdb2", SQL_NTS); SQLSetConnectAttr(ConHandle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER); SQLAllocHandle(SQL_HANDLE_STMT, ConHandle, &StmtHandle); SQLExecDirect(StmtHandle, "DELETE FROM tab1 WHERE empid = 1", SQL_NTS); Which of the following CLI/ODBC function calls will generate the return code SQL_ERROR? A. SQLEndTran(SQL_HANDLE_DBC, Conhandle, SQL_COMMIT) B. SQLEndTran(SQL_HANDLE_DBC, Conhandle, SQL_ROLLBACK) C. SQLExecute(StmtHandle) D. SQLFreeHandle(SQL_HANDLE_STMT, StmtHandle)
Answers Question 1
Question 2
Question 3
The correct answers are B and E. Every CLI application must begin by allocating an environment handle, and each data source connection is managed by a connection handle. Therefore, in order to allocate and use an SQL statement handle, an environment handle and at least one connection handle must first be allocated. The correct answer is A. Earlier versions of CLI/ODBC contained a function named SQLError() that could be used to obtain diagnostic information; however, there is no function named SQLGetError(). On the other hand, SQLGetDiagRec(), SQLGetDiagField(), and SQLGetSQLCA() can all be used to obtain diagnostic information when an SQL-related error occurs. (SQLGetSQLCA() can only be used to obtain information about the last SQL statement executed. Furthermore, this function has been depreciatedSQLGetDiagRec() and SQLGetDiagField() should be used instead.) The correct answer is D. A connection string is simply a series of keyword/value pairs, separated by semicolons, that contains information that is to be used by the SQLDriverConnect() function to establish a connection to a data source. Some of the more common keyword/value pairs used include: DSN=DataSourceName, UID=UserID, PWD=Password, and NEWPWD= NewPassword.
Question 4
Question 5
Question 6
Question 7
Question 8
Question 9
Question 10
Question 11
The correct answer is B. The SQL_NO_DATA return code is generated when a CLI/ODBC function completes successfully but no relevant data is found. Therefore, if SQLFetch() is executed when no more records are available, the return code SQL_NO_DATA will be returned. The correct answer is C. Read Stability is one of four transaction isolation levels available with DB2 UDB. (The four isolation levels available are: Repeatable Read, Read Stability, Cursor Stability, and Uncommitted Read.) The connection attribute used to specify the isolation level to use for a given data source connection is the SQL_ATTR_TXN_ISOLATION attribute. The correct answers are B and E. Three CLI/ODBC functions can be used to establish a connection to a data source. They are: SQLConnect(), SQLDriverConnect(), and SQLBrowseConnect(). The correct answer is D. Handles are initialized and their corresponding data storage areas are allocated by calling the SQLAllocHandle() function with the appropriate handle type (SQL_HANDLE_ENV [environment], SQL_ HANDLE_DBC, [connection], SQL_HANDLE_STMT [statement], SQL_HANDLES_DESC [descriptor]) and parent handle specified. The correct answer is C. An attempt to submit a query to a data source via a connection handle (as is the case in this example) will fail and cause the return code SQL_INVALID_HANDLE to be generated. That's because SQL statements must be sent to a data source for processing via a statement handlenot a connection handle. The correct answer is C. The key wording here is return values in upper case. To return values in upper case, the UCASE( ) function must be used to convert all values retrieved. If the UCASE( ) function is used in the WHERE clause of a SELECT statement, the conversion is applied for the purpose of locating matching valuesnot to return values that have been converted to upper case. The correct answer is A. The SQL_ATTR_CURSOR_TYPE statement attribute is used to specify the type of cursor to be used when processing result data sets. (A cursor can be defined as forward-only, static, keyset-driven, or dynamic.) The SQL_ATTR_CONCURRENCY statement attribute is used to specify the cursor concurrency level to use (read-only, low-level locking, or value-comparison locking), and the SQL_ATTR_CURSOR_SENSITIVITY statement attribute is used to specify whether cursors on a statement handle are to make changes made to a result data set by another cursor visible (insensitive or sensitive). There is no SQL_ATTR_CURSOR_MODE statement attribute. The correct answer is B. The line "SQLSetConnectAttr(ConHandle, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER);" tells the CLI/ODBC application to run in auto-commit mode. (When auto-commit mode is used, each SQL statement is treated as a complete transaction, and each transaction is automatically committed after the SQL statement successfully executes.) Therefore, every SQL statement processed is automatically committed after it is successfully executed. As a result, three records were added to the three
Question 12
Question 13
Question 14
Question 15
Question 16
Question 17
Question 18
records that already existed in table TAB1, then two records were deleted, leaving a total of four records in table TAB1. The correct answer is C. Cursors are automatically generated and opened when the SQLPrepare()-SQLExecute() or SQLExecDirect() function is executed, and a result data set containing zero or more rows is produced in response to the SQL statement that was prepared/executed. The correct answer is B. The SQL_ATTR_CONCURRENCY statement attribute is used to specify the cursor concurrency level to use (read-only, lowlevel locking, or value-comparison locking), and the SQLGetStmtAttr() function is used to obtain the value assigned to a statement attribute. The correct answer is C. When CLI/ODBC functions fail to execute correctly, diagnostic information that can help identify why the error occurred can be obtained by calling the SQLGetDiagRec() function and/or the SQLGetDiagField() function. These functions accept an environment, connection, statement, or descriptor handle as input and return diagnostic information about the last CLI/ODBC function executed, using the handle specified. In this case, the error is associated with a statement handle (remember that SQL statements are processed using statement handles), so SQLGetDiagRec() must be called with the statement handle that diagnostic information is to be obtained for. The correct answer is C. In order to update data from a CLI/ODBC application, an UPDATE SQL statement must be constructed, prepared, and executed. Once constructed, any SQL statement can be prepared and executed using the SQLPrepare() and SQLExecute() functions together or by using the SQLExecDirect() function, which combines the functionality of the SQLPrepare() and SQLExecute() functions into a single operation. The correct answer is D. A descriptor handle is a pointer to a data storage area that contains a collection of metadata that describes either the application variables that have been bound to the parameter markers in an SQL statement or the application variables that have been bound to the columns of a result data set produced in response to a query. The correct answers are B and D. When the SQLDriverConnect() function is called with a connection string that does not contain all the information needed to establish a data source connection, DB2 CLI will display a dialog that prompts the user for the missing informationprovided the SQLDriverConnect() function is called with the SQL_DRIVER_PROMPT, SQL_DRIVER_COMPLETE, or SQL_ DRIVER_COMPLETE_REQUIRED option specified. The correct answer is C. Because an SQL statement must be prepared by the SQLPrepare() function before it can be executed by the SQLExecute() function, the statement SQLExecute(StmtHandle) will fail when it is calledin this example, an SQL statement was executed directly with the SQLExecDirect() function, but no SQL statement was prepared for later execution with the SQLPrepare() function.
supportsOpenCursorsAcrossCommit() supportsOpenCursorsAcrossRollback() Statement PreparedStatement CallableStatement execute() executeQuery() executeUpdate() getResultSet() setCursorName() setMaxRows() ResultSet getMetaData() next() ResultSetMetaData getColumnName() getColumnType() isNullable() SQLException getMessage() getErrorCode() getSQLState() DataSource getConnection() PooledConnection DB2SimpleDataSource DB2DataSource DB2ConnectionPoolDataSource DB2XADataSource Class.forName() Static Initializer Uniform Resource Locator (URL) Parameter markers Exception Throwing an exception Exception Handler try catch finally SQLJ Declaration statements Executable statements SQLJ Clause Connection Context Default Context Execution Context Result set iterator Positioned iterator Named iterator Host Expression SQLJ Translator SQLJ serialized profile file DB2 SQLJ Profile Customizer db2profc db2sqljcustomize
Techniques you will master: Understanding the difference between JDBC and SQLJ. Knowing how some of the more common JDBC interfaces and classes are used to develop JDBC applications and applets. Knowing how some of the more common SQLJ interfaces and classes are used to develop SQLJ applications and applets. Knowing how to establish a database connection from a JDBC or an SQLJ application or applet. Knowing how to construct JDBC and SQLJ applications and applets from source code files. Knowing how to capture and process errors, as well as how to obtain diagnostic information when an error occurs. Recognizing how iterators are used in SQLJ applications and applets to obtain values from a result data set.
JDBC Drivers
To use the JDBC API with a particular relational database management system, you must have a JDBC technology-based driver that can mediate calls between JDBC and the database system being used. JDBC technology-based drivers generally fit into one of four categories: Type 1: JDBC-ODBC bridge plus ODBC driver. The JDBC-ODBC bridge driver is a product that provides JDBC access via ODBC drivers; this type of driver translates JDBC calls made from a Java application into corresponding ODBC function calls and forwards them to an ODBC Driver Manager for processing. This driver was originally designed to provide JDBC applications with a way to access data when no other drivers were available. Now that other drivers exist, use of the Type 1 driver should be restricted to prototyping. (Not only is it the slowest driver available but it is also the most limited in the functionality it provides.) Type 2: Partly Java-Partly Native API driver. This type of driver converts JDBC calls made from a Java application into DBMS-specific native-API calls and executes them. Like the JDBCODBC bridge driver, this driver requires some operating system-specific binary code to be stored on each client workstation used. (DB2 UDB Version 8.1 and earlier provides a Type 2 driver
named COM.ibm.db2.jdbc.app.DB2Driver, which is located in a file named db2java.zip. Because this driver is typically used with Java applications rather than with Java applets, it is often referred to as the "app" driver.) Type 3: JDBC-Pure Java driver and middleware server. This type of driver translates JDBC calls made from a Java program (typically an applet) into a DBMS-independent net protocol, which is then translated to a DBMS-specific protocol by a server. (DB2 UDB Version 8.1 and earlier provides a Type 3 driver named COM.ibm.db2.jdbc.net.DB2Driver, which is located in a file named db2java.zip. This driver communicates with a DB2 Java daemon process that translates JDBC calls into CLI. Because this driver is typically used over a network, it is often referred to as the "net" driver; because it is often used with JDBC applets rather than with JDBC applications, it is also referred to as the "DB2 JDBC applet" driver or simply "applet" driver.) Type 4: Native-Pure Java driver. This type of driver is written in pure Java and uses a DBMS vendor-specific protocol to establish a network connection directly to a specific DBMS. This driver requires the least amount of overhead and provides the most efficient solution to database access. It can also be used for any type of Java application or applet. (DB2 UDB Version 8.1, FixPak 2 and later provides a Type 4 driver named com.ibm.db2.jcc.DB2Driver, which is located in a file named db2jcc.jar. This driver is referred to as the DB2 Universal JDBC Driver.) The DB2 JDBC application, or "app," driver communicates directly with a DB2 UDB server, using CLI/ODBC function calls that are made through the DB2 UDB client libraries. As a result, this driver provides much better performance than the DB2 JDBC "applet," or "net," driver because communication does not have to go through the DB2 Java daemon. Figure 6-1 illustrates a typical DB2 JDBC environment in which the "app" driver is used.
Figure 6-1: DB2 JDBC Application ("app") driver environment. The DB2 JDBC "net" driver, however, communicates indirectly with a DB2 UDB server, using the DB2 Java daemon (also known as the DB2 JDBC Applet Server). Here's how it works: When a Java applet is encountered on a Web client, the client Web browser downloads the file db2java.zip just before the applet is executed. Then, the DB2 "net" driver establishes a TCP/IP connection with the DB2 Java daemon that is running on the Web Server host where the Web page containing the applet was served. The DB2 Java daemon then fulfills database requests on behalf of the applet. Figure 6-2 illustrates a typical DB2 JDBC environment in which the "net" driver is used.
Figure 6-2: DB2 JDBC Applet ("net") driver environment. Because the "app" driver provides much better performance than the "net" driver, it should be used whenever possible. However, keep in mind that the "app" driver cannot be used with Java applets because an applet is run on a client browser and most clients who access Web pages will not have the DB2 libraries installed that are needed to process a DB2 JDBC applet.
Table 6-1: JDBC Interfaces and Classes Found in the Java v1.4.2 java.sql Package Interface or Class Purpose supply properties for establishing connections use this class.) Connection interface SQL Statement Processing Statement interface PreparedStatement interface CallableStatement interface ResultSet interface Used to execute an SQL statement and obtain any results produced by the statement's execution. Used to represent a precompiled or prepared SQL statement. (This interface extends the Statement interface.) Used to invoke an SQL stored procedure. (This interface extends the Statement interface.) Used to retrieve and/or update a result data set, which is usually generated by executing an SQL statement that queries the connected data source. Establishes and manages a data source connection (session).
Savepoint interface
Used to provide the representation of a savepoint, which is a point within the current transaction that can be referenced from the Connection.rollback() method. When a transaction is rolled back to a savepoint, all changes made after that savepoint was created are undone. SQL Data Type-to-Java Data Type Mapping Array interface Blob interface Clob interface Date class Represents (maps) an SQL ARRAY value in a Java application or applet. Represents (maps) an SQL BLOB value in a Java application or applet. Represents (maps) an SQL CLOB value in a Java application or applet. Represents (maps) an SQL DATE value in a Java application or applet. (This class consists of a thin wrapper around a millisecond value that allows JDBC to identify it as an SQL DATE value.) Represents (maps) an SQL REF value, which is a reference to an SQL structured data type value in the connected data source, in a Java application or applet. Represents (maps) an SQL structured data type value in a Java application or applet. Represents (maps) an SQL TIME value in a Java application or applet. (This class consists of a thin wrapper around a java.util.Date value that allows JDBC to identify it as an SQL TIME value.) Represents (maps) an SQL TIMESTAMP value in a Java application or applet. (This class consists of a thin wrapper around a java.util.Date value that allows JDBC to identify it as a SQL TIMESTAMP value.) Defines the constants used to identify generic SQL data types (called JDBC types) in a Java application or applet.
Ref interface
Timestamp class
Types class
Table 6-1: JDBC Interfaces and Classes Found in the Java v1.4.2 java.sql Package Interface or Class Purpose SQL User-Defined Data Type-to-Java Data Type Mapping SQLData interface SQLInput interface SQLOutput interface Obtaining Metadata DatabaseMetaData interface ParameterMetaData interface ResultSetMetaData interface Trapping Errors and Warnings SQLException class SQLWarning class DataTruncation class An exception that provides information when there is a problem accessing data. An exception that provides information when a data source access warning is generated. An exception that reports a "Data Truncation" warning (on reads) or throws a "Data Truncation" exception (on writes) when JDBC unexpectedly truncates a data value. An exception that provides information when one or more commands in a batch update operation do not execute successfully. Used to specify the permission that the SecurityManager will check when code that is running an applet calls the DriverManager.setLogWriter method. Purpose An interface that registers to be notified of events generated by a PoolConnection object. Such events are generated by a JDBC driver when an application is finished using a data source connection or when a connection error occurs because the connection can no longer be used (for example, if a server crashes). A factory for PooledConnection objects. A factory for connections to the physical data source that the DataSource object represents. (A JDBC driver that is accessed via the DataSource interface does not Used to obtain comprehensive information about the connected data source as a whole. Used to obtain information about the data types and properties of the parameters used in a PreparedStatement object. Used to get information about the attributes (data types and properties) of the columns in a ResultSet object. Represents (maps) an SQL user-defined data type value in a Java application or applet. An input stream that contains a stream of values representing an instance of an SQL structured data type or distinct data type. An output stream that can be used to write the attributes of an SQL user-defined data type back to a connected data source.
Table 6-2: JDBC Interfaces and Classes Found in the Java v1.4.2 javax.sql Package Interface or Class Interfaces ConnectionEventListener
ConnectionPoolDataSource DataSource
Table 6-2: JDBC Interfaces and Classes Found in the Java v1.4.2 javax.sql Package Interface or Class Purpose automatically register itself with the DriverManager class.) PooledConnection RowSet An interface that provides hooks for connection pool management. An interface that adds support to the JDBC API for the JavaBeans component model. (The RowSet interface is unique in that its implementation is a layer of software that executes "on top" of a JDBC driver.) An interface that a RowSet object implements to present itself to a RowSetReader or RowSetWriter object. An interface that must be implemented by any component that wants to be notified when a significant event happens in the life of a RowSet object. An interface used to get information about the columns in a RowSet object. A special interface that a disconnected RowSet object calls on to populate itself with rows of data. A special interface that provides methods used to update rows in a RowSet object. (For example, the RowSetWriter.writeRow() method is called internally by RowSet.acceptChanges() for each row that is inserted, updated, or deleted in a RowSet object.) An interface that provides support for distributed transactions. A factory for XAConnection objects (that is used internally). An event object that is used to provide information about the source of a connection-related event. ConnectionEvent objects are generated when an application closes a pooled connection and when an error occurs. An event object that is generated when an event occurs to a RowSet object. RowSetEvent objects are generated when a single row in a rowset is changed, the whole rowset is changed, or the rowset cursor is moved.
RowSetInternal RowSetListener
RowSetEvent
Of course, the real heart of the JDBC API is not so much the interfaces and classes themselves, but rather the various methods that are encapsulated within each interface and class provided. Therefore, to develop any type of Java application or applet that utilizes the JDBC API, you must be familiar with some of the more common interface and class methods available. The DriverManager Class Earlier, we saw that in order to use the JDBC API to interact with a relational database management system, a JDBC technology-based driver must be available to mediate calls between the Java application or applet and the database system being used. Four types of drivers are available, and before any driver can be used, it must be registered with a JDBC
DriverManager class. (The DataSource interface, which is a new interface introduced in the JDBC 2.0 API, is an alternative to the DriverManager class.) Registration is performed at the beginning of a JDBC application or applet by calling the static forName() method of the Class object and specifying the fully-qualified name of the JDBC-based driver to be registeredthis action will result in the creation of a corresponding DriverManager object. By far, the most important DriverManager class method available is the getConnection() method. An instance of the Connection interface is created by calling this method and passing it a Uniform Resource Locator (URL) that identifies the data source a connection is to be established to, along with any other appropriate information such as the authorization ID and password that is to be used to establish the connection. As part of the Connection interface initialization, the DriverManager class attempts to locate a JDBC driver that can be used to connect to the desired data source, and if a suitable driver is identified, a data source connection is established. From that point on, the connection to the data source is represented by its associated Connection object.
getWarnings(). Returns the first warning reported for the Connection object. close(). Terminates a data source connection and releases a Connection object's JDBC resources immediately instead of waiting for them to be released automatically. Note If a JDBC object is not explicitly closed when it is no longer needed, the Java Virtual Machine's garbage collector thread will eventually release its associated resources during a lull in system activity or if memory resources on the database server are constrained. However, it is generally good practice to release JDBC resources as soon as you are finished with them by calling a JDBC object's close() method when the object is no longer needed. The DatabaseMetaData Interface A DatabaseMetaData object is used to obtain information about the data source that a JDBC application has established a connection to. Some of the more common DatabaseMetaData interface methods used include the following: getTables(). Obtains a list of all tables that have been defined in the data source. getColumns(). Obtains a list of a table's indices and corresponding statistics. getIndexInfo(). Obtains a list of all columns that have been defined in the data source. getTypeInfo(). Obtains a list of all data types recognized by the data source. supportsOpenCursorsAcrossCommit(). Indicates whether open cursors will persist across transaction commit boundaries. supportsOpenCursorsAcrossRollback(). Indicates whether open cursors will persist across transaction rollback boundaries. The Statement Interface Like a CLI/ODBC statement handle, a JDBC Statement object represents a single SQL statement that is to be processed. And once created, a Statement object can be used both to execute an SQL statement and to obtain any results produced by the statement's execution. Some of the more common Statement interface methods used include the following: execute(String). Executes an SQL statement that may or may not produce a result data set. (This method returns a boolean value of TRUE if a result data set is produced as a result of the SQL statement specified being executed; depending on the value returned, you may decide to invoke other methods such as getResultSet() and getUpdateCount().) executeQuery(String). Executes an SQL query (i.e., a SELECT or a VALUES SQL statement) and returns a ResultSet object containing the results of the query. executeUpdate(String). Executes an INSERT, UPDATE, or DELETE SQL statement. This method returns a count of the rows that were modified (if any) when the SQL statement was executed. getResultSet(). Creates a ResultSet object that has its own set of methods that can be used to extract specific values from the result data set produced in response to a query. setCursorName(String). Assigns a name to the cursor associated with the result set produced by either the execute() or the executeQuery() method. Once defined, a cursor name can be used to perform positioned update and positioned delete operations.
setMaxRows(int). Specifies the maximum number of rows that any ResultSet object is allowed to contain. getWarnings(). Returns the first warning reported for the Statement object. close(). Releases a Statement object's JDBC resources immediately instead of waiting for them to be released automatically. The Statement interface has two important sub interfaces: PreparedStatement, which represents an SQL statement that has been prepared for execution, and CallableStatement, which represents an SQL statement that is to be used to invoke a stored procedure. Because both of these interfaces are derived from the Statement interface, they inherit all of the methods the Statement interface provides and they contain additional methods of their own, many of which are used to assign values to parameter markers used in the statement. (Many of these methods can be seen in Table 6-3.) Table 6-3: DB2 SQL-to-JDBC Data Type Mappings and Their Corresponding Data Movement Methods SQL Data Type Compatibl e Java Data Type or Class short int BigDecimal PreparedStatement/Ca llableStatement Interface Input Method setShort() setInt() setBigDecimal() ResultSet Interface Output Method getShort() getInt() getBigDecimal()
SMALLINT INTEGER or INT DECIMAL, DEC, NUMERIC, or NUM REAL or FLOAT DOUBLE, DOUBLE PRECISION, or FLOAT CHARACTER or CHAR CHARACTER VARYING, CHAR VARYING, or VARCHAR LONG VARCHAR
float double
setFloat() setDouble()
getFloat() getDouble()
getString() getString(), getASCIIStream() , or getUnicodeStream () getASCIIStream() or getUnicodeStream () getByte() or getBytes() getBytes() getBytes() or getBinaryStream(
InputStrea m
setASCIIStream() or setUnicodeStream()
Table 6-3: DB2 SQL-to-JDBC Data Type Mappings and Their Corresponding Data Movement Methods SQL Data Type Compatibl e Java Data Type or Class m PreparedStatement/Ca llableStatement Interface Input Method ResultSet Interface Output Method )
VARCHAR FOR BIT DATA GRAPHIC VARGRAPHI C LONG VARGRAPHI C DATE TIME TIMESTAMP BLOB CLOB DBCLOB
byte or byte[] byte[] byte[] or InputStrea m Date Time Timestamp Blob Clob InputStrea m
setByte() or setBytes() setBytes() setBytes() or setBinaryStream() setDate() setTime() setTimestamp() setBlob() setClob() setBinaryStream()
getByte() or getBytes() getBytes() getBytes() or getBinaryStream( ) getDate() getTime() getTimestamp() getBlob() getClob() getBinaryStream( )
Table 6-4: JDBC DataSource Interface Properties Property Description Data Type String Method Used to Retrieve or Set Property Value getServerName(), setServerName()
serverName
The name assigned to the server where the data source a connection is to be established with resides. The database server listener port number. (With DB2 UDB servers, this is the port number that the DB2 Java daemon monitors for database connection requests.) The alias assigned to the data source a connection is to be established with. The authorization ID that is to be used to establish a data source connection. The password that is to be used to establish a data source connection. Descriptive information about the data source.
portNumber
int
getPortNumber(), setPortNumber()
databaseName
String
user
String
password
String
description
String
Once created, a DataSource object can be thought of as a factory that facilitates connections to the data source that the DataSource object represents. In a basic DataSource object implementation, the getConnection() method of a Data-Source object is used to create a Connection object that provides a physical connection to a particular data source. In a more complex implementation, a DataSource object may also implement connection pooling for the data source it represents. In this case, the getConnection() method returns a handle to a PooledConnection object rather than to a Connection object. An application uses the resulting PooledConnection object like any other connectionconnection pooling has no effect on application code except that a pooled connection, like all connections, should be explicitly closed when it is no longer needed. (When an application closes a connection that is pooled, the connection is returned to a pool of reusable connections, and the next time the getConnection() method is called, a handle to one of these pooled connections will be returned if one is available. Because connection pooling reduces the need to create a new physical connection every time one is requested, it can help applications run significantly faster.) When the DB2 Universal JDBC driver is used instead of the "app" or "net" driver, several implementations of the DataSource interface are provided: DB2SimpleDataSource. Provides the same functionality as the DataSource interface but does not support connection pooling. DB2DataSource. Provides the same functionality as the DataSource interface and provides connection pooling support. (With this implementation, connection pooling is handled internally and is transparent to the application that uses it.)
DB2ConnectionPoolDataSource. Provides the same functionality as the DB2DataSource interface; however, with this implementation, you must manage the connection pooling yourself, by either writing your own code or by using a tool such as WebSphere Application Server. DB2XADataSource. Provides the same functionality as the DataSource interface along with connection pooling and supports distributed transactions (also known as two-phase commits). With this implementation, you must manage the distributed transactions and connection pooling yourself, by either writing your own code or by using a tool such as Web-Sphere Application Server. (If you are using an XA-compliant transaction manager, such as IBM WebSphere, BEA Tuxedo, or Microsoft Transaction Server, the number of transactions that can be running at one time is limited only by the resources available.)
Finally, the DB2 Universal JDBC driver is registered by making a Class.forName() method call that looks like this: Class.forName("com.ibm.db2.jcc.DB2Driver") Because the Class object is part of Java and not JDBC, it can be used to load any class that can be found in a workstation's class path (which is defined using an environment variable). When a Class object is created, the Java Virtual Machine is responsible for initializing the class unless the class provides its own initialization code, referred to as the "static initializer". In a JDBC driver, the static initializer is responsible for registering the newly-loaded driver with JDBC at run time.
Identifies the name assigned to the data source with which a connection is to be established. Identifies the name assigned to the server where the data source specified in the DataSource parameter resides. Identifies the listener port number being used by the DB2 Java daemon on the server upon which the data source specified resides. (If no port number is specified, the default port number 6789 is used.)
Additional information such as the authorization ID and corresponding password that is to be used to establish the connection can also be specified as input parameter values for the getConnection() method. When the getConnection() method is invoked, the DriverManager class attempts to connect to the data source specified using a JDBC driver that has already been registered. Once a suitable driver is identified, a data source connection is established and a Connection interface object is created (if no driver is found, an exception will be thrown). From that point on, the connected data source is represented by its corresponding Connection object.
Transaction Processing
Like CLI/ODBC applications, the bulk of a JDBC application or applet is made up of high-level programming language statements (in this case, Java) that invoke functions (or methods) to perform SQL operations against a connected data source. These SQL operations can be performed individually or they can be grouped together as a single unit of work (otherwise known as a transaction). In either case, the following steps must be carried out in order to perform most SQL operations: 1. Create a JDBC Statement, PreparedStatement, or CallableStatement object and associate it with an SQL statement. 2. Provide values for any parameter markers used. 3. Execute the SQL statement. 4. Retrieve and process any results produced.
5. 6.
Terminate the current transaction by committing it or rolling it back. Release all resources used.
The following sections describe each of these steps in more detail. Creating a JDBC Statement, PreparedStatement, or CallableStatement
Object
Before any SQL statement can be executed by a JDBC application or applet, it must be assigned to a JDBC Statement, PreparedStatement, or CallableStatement object. Like CLI/ODBC statement handles, Statement, PreparedStatement, and CallableStatement objects contain detailed information about a particular SQL statement information that a data source needs in order to process the statement. Just as SQL statements can be processed in embedded SQL and CLI/ODBC applications in one of two ways, SQL statements can be processed in a JDBC application or applet in one of two ways: Prepare then Execute. This approach separates the preparation of the SQL statement from its execution and is typically used when an SQL statement is to be executed repeatedly. This method is also used when an application needs advance information about the columns that will exist in the result data set produced when the SQL statement is executed. Execute. This approach combines the preparation and execution of an SQL statement into a single step and is typically used when an SQL statement is to be executed only once. This method is also used when the application does not need additional information about the result data set that will be produced, if any, when the SQL statement is executed. The method used determines which type of object is needed: If the preparation of an SQL statement is to be separated from its execution, a PreparedStatement object should be used. On the other hand, if the preparation and execution of an SQL statement is to be combined into a single step, a Statement object should be used. (If the SQL statement will invoke a stored procedure, a CallableStatement object should be used.) Statement objects are created by calling the createStatement() method of the appropriate Connection object; the SQL statement that is to be associated with a particular Statement object must be specified as an input parameter value to many of the Statement object methods available. In contrast, PreparedStatement objects are created by calling the prepareStatement() method of the appropriate Connection object; in this case, the corresponding SQL statement must be provided (as an input parameter value) when the prepareStatement() method is invoked. (CallableStatement objects are created by calling the prepareCall() method of the appropriate Connection object with the corresponding SQL statement specified as an input parameter value.)
(assuming that Stmt refers to a PreparedStatement or CallableStatement object). By using parameter markers and appropriate setXXX() methods, an application can execute a single SQL statement multiple times and obtain different results each time simply by modifying the value assigned to any parameter marker between each execution. Because the access plan used to process such an SQL statement is cached in memory at the time the statement is prepared, subsequent executions require little or no additional overhead, resulting in an improvement in application performance.
In the third step, when all column values for a particular row have been acquired, the cursor is positioned on the next row in the result data set by calling the next() method and the process of retrieving column values is repeated. Usually, this entire process is repeated as long as there are unprocessed rows in the result data set.
Cleaning up Resources
One step any well-behaved application will perform is to clean up any resources that were allocated or acquired on its behalf when those resources are no longer needed. In some situations, it is possible to ignore this step because the Java Virtual machine, the operating system, or both automatically free any resources acquired when an application terminates. However, things can be very different if an application, or particularly an applet, is running in a "long-life" environment such as a Web application server. The Java Virtual Machine contains a mechanism known as the "garbage collector" that periodically locates and cleans up any unused objects it happens to find lying around. Some may argue that because the garbage collector exists, Java applications and applets do not have to be as diligent about resource clean-up as other applications. However, there is no way to tell when the garbage collector will spring into action, and an application may run out of resources long before that point. Additionally, because JDBC applications interface with one or more external systems (data sources), they are often required to use and maintain external resources that may be somewhat limited. For example, the number of open PreparedStatement or ResultSet objects allowed may be (and in fact, probably is) limited by the JDBC-technology based driver being used. Therefore, when the results of an SQL statement have been processed and the corresponding JDBC Statement, PreparedStatement, or CallableStatement object used is no longer needed, that object should be closed so that any resources reserved on its behalf
are freed. The same is true for any ResultSet objects that are produced in response to a query. To clean up and release the resources used by a Statement, PreparedStatement, CallableStatement, or ResultSet object, simply call that object's close() method. However, because ResultSet objects have a dependency on Statement or PreparedStatement objects, and because these objects, in turn, have a dependency on Connection objects, a ResultSet object will be closed automatically if its associated Statement or PreparedStatement object is closed. Likewise, a Statement, PreparedStatement, or CallableStatement object will automatically be closed when its associated Connection object is closed.
try { // Declare The Local Memory Variables Connection ConObject; Statement StmtObject; ResultSet Results; String ConURL; String UserID; String Password; String SQLStmt; String FName; String LName; double Salary; // Create The Connection URL And Establish A // Connection To The SAMPLE Database ConURL = "jdbc:db2:SAMPLE"; UserID = "db2admin"; Password = "ibmdb2"; ConObject = DriverManager.getConnection(ConURL, UserID, Password); // Use Manual-Commit Mode ConObject.setAutoCommit(false); // Create And Execute A SELECT SQL Statement SQLStmt = "SELECT * FROM EMPLOYEE "; SQLStmt += "WHERE WORKDEPT='D11'"; StmtObject = ConObject.createStatement(); Results = StmtObject.executeQuery(SQLStmt); // Print A Header System.out.println("\nEmployee Information:\n"); System.out.println("Name\t\t\tSalary"); System.out.print("--------------------\t"); System.out.println("----------"); // While There Are Records, Process Them while (Results.next()) { // Retrieve A Record FName = Results.getString(2);
LName = Results.getString(4); Salary = Results.getDouble(12); // Print The Record Retrieved System.out.print(FName + " " + LName + "\t"); if (FName.length() + LName.length() < 13) System.out.print("\t"); System.out.println("$ " + Salary + "0"); } // Issue A Commit To Free All Locks ConObject.commit(); // Close The Result Data Set And SQL Statement // Objects Results.close(); StmtObject.close(); // Close The Database Connection ConObject.close(); } catch(SQLException E) { System.out.println ("SQL Error encountered."); System.out.println ("Error msg: " + E + ". SQLSTATE = " + E.getSQLState() + " Error code = " + E.getErrorCode()); E.printStackTrace(); } catch (Exception E) { System.out.println("Error encountered."); E.printStackTrace(); } } } A simple JDBC applet that performs the same operation (obtains and prints the names and salaries of every employee that works in department D11) might look something like this: // Import The JDBC Driver Manager Packages/Classes
import java.sql.*; import java.awt.*; import java.applet.Applet; // Define The TestApplet Class public class TestApplet extends Applet { static { // Register The DB2 UDB JDBC Net Driver try { Class.forName("COM.ibm.db2.jdbc.net.DB2Driver"); } catch (ClassNotFoundException E) { System.out.println("Error registering driver."); E.printStackTrace(); } } // Declare The Global Memory Variables Connection ConObject; /*-----------------------------------------------------*/ /* Define The Initialization Method -- This Is The */ /* First Method That Gets Called When The Applet Is */ /* Downloaded public void init() { try { // Declare The Local Memory Variables String ConURL; String Server; String Port; String UserID; String Password; // Get All Parameter Values Passed From The */ /*-----------------------------------------------------*/
// Calling HTML Page Server = getParameter("server"); Port = getParameter("port"); UserID = "db2admin"; Password = "ibmdb2"; // Create The Connection URL ConURL = "jdbc:db2://" + Server + ":" + Port; ConURL += "/SAMPLE"; // Establish A Connection To The SAMPLE Database ConObject = DriverManager.getConnection(ConURL, UserID, Password); // Use Manual-Commit Mode ConObject.setAutoCommit(false); } catch(Exception E) { System.out.println("Unable to connect."); E.printStackTrace(); } } /*-----------------------------------------------------*/ /* Define The Screen Painting Method -- This Method */ /* Retrieves Data From The Database And Displays It */ /*-----------------------------------------------------*/ public void paint(Graphics Output) { try { // Declare The Local Memory Variables Statement StmtObject; ResultSet Results; String SQLStmt; String FName; String LName; double Salary; int YCoordinate;
String Outtext; // Create And Execute A SELECT SQL Statement SQLStmt = "SELECT * FROM EMPLOYEE "; SQLStmt += "WHERE WORKDEPT='D11'"; StmtObject = ConObject.createStatement(); Results = StmtObject.executeQuery(SQLStmt); // Print A Header Output.drawString("Employee Information:", 20, 40); Output.drawString("Name", 20, 65); Output.drawString("Salary", 180, 65); Output.drawString("--------------------", 20, 75); Output.drawString("---------------", 100, 75); Output.drawString("----------------", 180, 75); // While There Are Records, Process Them YCoordinate = 90; while (Results.next()) { // Retrieve A Record FName = Results.getString(2); LName = Results.getString(4); Salary = Results.getDouble(12); // Display The Record Retrieved Outtext = FName + " " + LName; Output.drawString(Outtext, 20, YCoordinate); Outtext = "$ " + Salary + "0"; Output.drawString(Outtext, 180, YCoordinate); YCoordinate += 20; } // Issue A Commit To Free All Locks ConObject.commit(); // Close The Result Data Set And SQL Statement // Objects
Results.close(); StmtObject.close(); } catch(SQLException E) { System.out.println ("SQL Error encountered."); System.out.println ("Error msg: " + E + ". SQLSTATE = " + E.getSQLState() + " Error code = " + E.getErrorCode()); E.printStackTrace(); } catch(Exception E) { System.out.println("Error encountered."); E.printStackTrace(); } } } Keep in mind that although a JDBC application can be executed from a system command prompt, a JDBC applet can only be called from an HTML page that is accessed by a Javaenabled Web browser. The following HTML code illustrates how the JDBC applet just shown could be invoked: <html> <!-- Before running this code, modify the values of the <!-- parameters used as follows: --> --> --> -->
<!-- Assign the name of the database server where the DB2 <!-- SAMPLE database resides to "server" and assign the --> <!-- port number that DB2 JDBC server daemon is listening <!-- on (6789 is the default) to "port" <head> <title>DB2 JDBC Sample Applet </title> </head> <body> <h1> <center>DB2 JDBC Sample Applet </center></h1> <center> <applet code="TestApplet.class" width=325 height=375 archive="db2java.zip"> -->
<param name=server value='DB_SERVER'> <param name=port value='6789'> </applet> </center> </body> </html>
Identifies one or more program statements that make up the try block, catch block, or finally block. Identifies a specific type of exception that the exception handler (identified by the catch block) is designed to trap and process. Identifies the name that is to be assigned to the exception handler.
The following is a simple example of a try block and its associated catch block (exception handler). In this example, an attempt to load the DB2 UDB JDBC application driver is made; if the driver is not found, the catch block displays an error message and prints a stack trace. (If the driver is loaded successfully, the catch block is never executed.) // Register The DB2 UDB JDBC Application Driver try { Class.forName("COM.ibm.db2.jdbc.app.DB2Driver"); } catch (ClassNotFoundException E) { System.out.println("Error registering driver."); E.printStackTrace(); } Similar code usually appears at the beginning of any JDBC application or applet that is designed to interact with a DB2 UDB
Figure 6-3: Converting a JDBC source code file into an executable application or applet. It is important to note that when source code files are written in other high-level programming languages and compiled to produce an executable application, the compiled code is customized to run on a specific hardware platform. As a result, the application produced will be optimized for that particular environment but will have to be recompiled, and in some cases modified, before it can be used on a different platform. As a platform-independent environment, the Java platform can cause applications to be a bit slower than similar applications that are platform specific. However, smart compilers, well-tuned interpreters, and just-in-time bytecode compilers can bring a Java application's performance close to that of a similar platform-specific application's performance without threatening portability.
An Introduction to SQLJ
Earlier, we saw that dynamic SQL provides database applications with flexibility that cannot be obtained when static SQL is used. However, this flexibility does not come without costbecause the work of analyzing SQL statements to select the optimum data access plan to use for statement execution is done at application run time, dynamic SQL statements can take longer to execute than their equivalent static SQL counterparts. Unfortunately, although the option of using static SQL or dynamic SQL has been available to other high-level programming language developers for quite some time, Java application developers were initially forced to work exclusively with dynamic SQL. This restriction meant that in order to develop a Java application that interacted with a database, a developer had to master the JDBC API because embedding SQL statements directly in a Java source code file was not an option. To get around this limitation, The SQLJ Groupa consortium composed of IBM, Informix, Oracle, Sybase, Tandem, and Sun Microsystemsdeveloped a specification known as SQLJ (SQL-Java). SQLJ provides a way for Java developers to embed static SQL statements in a Java application. Eventually, The SQLJ Group submitted their SQLJ specification to the INCITS Technical Committee H2 on Database (which is the committee that is responsible for developing
standards for the syntax and semantics of database languages), and in 1999, SQLJ was formally adopted into the SQL:1999 standard. The SQLJ specification consists of three parts: Part 0: Embedded SQL in Java. This part specifies the SQLJ language syntax and semantics used to embed SQL statements in a Java application. Part 0 supports the use of static SQL statements in Java; however, it does not support the use of dynamic SQL statementsdynamic SQL is still handled by JDBC. Part 0 does support the mixing of embedded static SQL statements with JDBC APIs and it supports the same mapping between Java data types and SQL data types that is defined by JDBC. Part 1: SQL routines and Java. This part specifies extensions that allow SQL code to invoke methods or stored procedures that are written in Java. To support Part 1, a DBMS must have a Java Virtual Machine associated with it. And, in order to provide interaction between SQL functions and Java methods, each SQL parameter used, along with any return values generated, must be able to be mapped to respective Java method parameters and return values. Part 1 deals only with static SQL Java methods. Part 2: SQL data types and Java. This part defines SQL extensions for using Java classes as data types in SQL. Part 2 allows the mapping of SQL:1999 User-Defined Data Types (UDTs) to Java classes. It also allows the importing of a Java package into a database by defining tables that contain columns whose data type are specified to be a Java class. Part 2 adds non-static method support to the static method support provided in Part 1. It is important to note that SQLJ is not a replacement for JDBC. In fact, SQLJ run-time classes execute their queries using JDBC APIs. Later, when we look at the steps needed to compile and run an SQLJ application, we'll see how it is possible for SQLJ to provide static SQL support even though it functions as a layer on top of JDBC.
are used for execution control, execution status, and execution cancellation. Execution control methods are used to modify the semantics of subsequent SQL operations that will be executed on the context. Execution status methods are used to describe the results of the last SQL operation executed on the context. Execution cancellation methods are used to terminate the current SQL operation being executed on the context. Looking back at our example of a simple SQLJ clause, you will notice that no context has been specified. Such a clause would rely on the default context for connection information. An SQLJ clause that is associated with a specific connection context consists of the "#sql" token, followed by the ID of the desired connection context enclosed in brackets ([]), followed by an executable SQL statement enclosed in curly braces ({ }). Thus, an SQLJ clause designed to delete all rows from a table named EMPLOYEE, using a connection context named ConnectionCtx, would look like this: #sql [ConnectionCtx] { DELETE FROM EMPLOYEE }; Although JDBC applications rely on ResultSet objects to obtain and process the results of a query, SQLJ applications rely on objects known as result set iterators (or iterators) for this functionality. Specifically, a result set iterator is a special interface that is used to iterate (move back and forth) over the contents of a result data set produced in response to a query. Two types of iterators exist: positioned and named. Positioned iterator. A positioned iterator is a special result set iterator that employs a byposition binding strategy. Positioned iterators depend on the position of the columns of the data to which they are bound, as opposed to the names of the columns to which they are bound. Named iterator. A named iterator is a special result set iterator that employs a by-name binding strategy. Named iterators depend on the name of the columns of the data to which they are bound, as opposed to the position of the columns to which they are bound.
Identifies the name to be assigned to the iterator class being defined. Identifies one or more Java data types that correspond to the SQL data types used by the columns in a result data set.
Thus, a positioned iterator for a result data set that contains one column containing CHAR data values and another column containing DATE data values would be defined by executing an SQLJ definition statement that looks something like this: #sql iterator PositionItr(String, Date); Named iterators, however, identify the columns of a result data set by their column names; when you declare a named iterator (and consequently create a named iterator class), you must specify names for each iterator column, and every name provided must match the name of a column in the result data set that an instance of the named iterator class will be associated with. (An iterator column name and a result table column name that differ only in case are considered to be matching names.)
The basic syntax used to define a named iterator is: #sql iterator [IteratorName]( [[DataType][ColumnName]], ...); where: IteratorName DataType ColumnName
Identifies the name to be assigned to the iterator class being defined. Identifies one or more Java data types that correspond to the SQL data types used by the columns in a result data set. Identifies one or more column names that correspond to the names assigned to the columns in a result data set.
Thus, a named iterator for a result data set that contains one column named LASTNAME that contains CHAR data values and another column named HIREDATE that contains DATE data values would be defined by executing an SQLJ definition statement that looks something like this: #sql iterator NamedItr(String Lastname, Date Hiredate); When a named iterator class is created as the result of a named iterator class declaration, individual accessor methods are created as well. One accessor method is created for each column of the iterator, and each accessor method created is assigned the same name as the corresponding iterator column. Once created, these accessor methods are used to retrieve data from columns of the result data set that the iterator is ultimately associated with. Going back to our previous example, when the following SQLJ definition statement: #sql iterator NamedItr(String Lastname, Date Hiredate); is executed, not only is a named iterator called NamedItr created, but two accessor methods, named Lastname() and Hiredate(), are created as well. The Lastname() accessor method is used to retrieve values from the first column of the corresponding result data set while the Hiredate() accessor method is used to retrieve values from the second column of the corresponding result data set. Note A result set iterator can be declared in a variety of ways. However, because a Java class underlies each iterator created, you need to ensure that when an iterator is declared, the resulting class obeys Java rules. For example, iterators that contain a WITH clause must be declared as "public." If an iterator needs to be public, it must be declared where a public class declaration is allowed.
Cleaning up Resources
Earlier, we saw that a well-behaved application will clean up any resources that were allocated or acquired on its behalf when those resources are no longer needed. Usually, no resource allocation is involved with processing SQLJ clauses; however, that is not the case when connection contexts and result set iterators are used. Context and iterator objects require resources, and these resources should be released when they are no longer needed. Like JDBC applications, SQLJ applications can count on the Java Virtual Machine garbage collector to close any unused objects that it happens to find lying around. However, as we saw earlier, when that approach is used, there is no way to tell when the garbage collector will spring into action, and an application can run out of resources long before garbage collection takes place. Therefore, whenever a context or iterator object is no longer needed, it should be closed so that any resources reserved on its behalf are freed immediately. To clean up and release the resources used by any context or iterator object, simply call that object's close() method.
E.printStackTrace(); } } /*-----------------------------------------------------*/ /* The Main Function */ /*-----------------------------------------------------*/ public static void main(String argv[]) { try { // Declare The Local Memory Variables String ConURL; String UserID; String Password; String Server; String Port; String EmpName; // Initialize The Local Memory Variables UserID = "db2admin"; Password = "ibmdb2"; Server = "DB_SERVER"; Port = "6789"; // Create The Connection URL ConURL = "jdbc:db2://" + Server + ":" + Port; ConURL += "/SAMPLE"; // Create A Connection Context Object And // Connect To The SAMPLE Database Using It // (Use Manual-Commit Mode) Ctx ConnectCtx = new Ctx(ConURL, UserID, Password, false); // Create A Named Iterator Object NamedIterator NamedItr; // Execute A Query And Asign The Result Data Set // Produced To The Named Iterator Object Created #sql [ConnectCtx] NamedItr = { SELECT LASTNAME, FIRSTNME, SALARY FROM EMPLOYEE WHERE
WORKDEPT='D11' }; // Print A Header System.out.println("\nEmployee Information:\n"); System.out.println("Name\t\t\tSalary"); System.out.print("-------------------\t"); System.out.println("----------"); // As Long As There Are Records Available, // Move The Named Iterator Through The Result // Data Set Produced And Display Them while (NamedItr.next()) { EmpName = NamedItr.Firstnme() + " " + NamedItr.Lastname() + "\t"; System.out.print(EmpName); if (EmpName.length() < 17) System.out.print("\t"); System.out.println("$ " + NamedItr.Salary() + "0"); } // Close The Named Iterator Object NamedItr.close(); // Issue A Commit To Free All Locks #sql [ConnectCtx] { COMMIT }; // Close The Connection Context Object // And Terminate The Database Connection ConnectCtx.close(); } catch(SQLException E) { System.out.println ("SQL Error encountered."); System.out.println ("Error msg: " + E + ". SQLSTATE = " + E.getSQLState() + " Error code = " + E.getErrorCode()); E.printStackTrace(); }
Figure 6-4: Converting an SQLJ source code file into an executable application or applet. Note that because the DB2 SQLJ Profile Customizer augments an SQLJ profile with DB2-specific information that is to be used at application run time, it should be invoked sometime after an SQLJ application has been translated but before the application is run
Practice Questions
Question 1 For which of the following is the CallableStatement object used? A. To execute a query B. To execute a stored procedure C. To perform an update operation D. To communicate with the system catalog Which of the following capabilities is provided by SQLJ but not JDBC? A. Ability to use the DataSource interface to establish a connection B. Ability to execute stored procedures C. Ability to issue dynamic queries against a DB2 database D. Ability to issue static queries against a DB2 database Given the following table definition: CREATE TABLE employees (empid INTEGER, name (VARCHAR(20)) Assuming the following statements execute successfully in a JDBC application: String SQLString = "INSERT INTO employees VALUES (?, ?)"; PreparedStatement PStmt = Con.prepareStatement (SQLString); Which of the following is the correct way to provide values for the parameter markers used?
Question 2
Question 3
Question 4
Question 5
Question 6
Question 7
Question 8
A. PStmt.execute(20, JAGGER); B. SQLString.execute(20, JAGGER); C. PStmt.setInt(1, 20); PStmt.setString(2, JAGGER); D. SQLString.setInt(1, 20); SQLString.setString(2, JAGGER); Which of the following objects is used by an SQLJ application to scroll through the results of a query? A. An iterator B. A context C. A ResultSet object D. A Statement object Assuming the following code executes without throwing any exceptions: Class.forName("COM.ibm.db2.jdbc.app.DB2Driver"); ConURL = "jdbc:db2:SAMPLE"; UserID = "db2admin"; Password = "ibmdb2"; Connection Con = DriverManager.getConnection(ConURL, UserID, Password); Which of the following can be used to determine if any warnings were generated? A. SQLWarning Warn = Con.getWarnings(); if (Warn != null) System.out.println(Warning!); B. SQLWarning Warn = Con.getMessage(); if (Warn != null) System.out.println(Warning!); C. SQLWarning Warn = DriverManager.getWarnings(); if (Warn != null) System.out.println(Warning!); D. SQLException Warn = Con.getExceptions(); if (Warn != null) System.out.println(Warning!); Which two of the following commands are used to customize an SQLJ profile? A. sqlj B. db2profc C. db2prepare D. sqljcustomize E. db2sqljcustomize Which of the following is NOT a DataSource object property that must be set in order to establish a DB2 JDBC connection to a remote database? A. serverName B. portNumber C. networkProtocol D. databaseName Which of the following JDBC objects can be used to get information about the data types that have been assigned to each column returned when the following query is executed? SELECT * FROM DEPARTMENTS A. Statement B. ResultSet C. DatabaseMetaData
Question 9
Question 10
Question 11
Question 12
Question 13
Question 14
Question 15
D. ResultSetMetaData A JDBC application requires connection pooling and the ability to support distributed transactions. Which of the following DB2 implementations of the DataSource interface should be used by this application? A. DB2DataSource B. DB2XADataSource C. DB2ConnectionPoolDataSource D. DB2SimpleDataSource Which of the following JDBC object methods can be used to obtain DB2-specific error values if the execution of an update operation fails? A. Statement.getErrorCode() B. PreparedStatement.getSQLCA() C. ResultSet.getError() D. SQLException.getErrorCode() Which of the following lines of code could only be found in an SQLJ applet? A. System.out.println(Unable to connect.); B. Class.forName(COM.ibm.db2.jdbc.net.DB2Driver ); C. #sql [ConCtx] NamedItr = { SELECT * FROM EMPLOYEE }; D. ConObject.setAutoCommit(false); Which of the following JDBC interfaces contains methods that can be called to determine whether or not cursors will remain open across transaction boundaries? A. Statement B. ResultSet C. ResultSetMetaData D. DatabaseMetaData Which of the following JDBC objects can be used to execute the following SQL statement? SELECT DEPTNO, DEPTNAME FROM DEPARTMENTS A. DriverManager B. Statement C. PreparedStmt D. ResultSet When building an SQLJ application, which of the following commands must be executed in order to generate an SQL package? A. db2jsqlcustomize B. db2profp C. sqlj D. sqljbind Which two of the following are a valid sequence for defining the URL string that is to be used to connect to a data source using a DB2 JDBC driver? A. jdbc:db2:[DataSource] B. db2:jdbc:[DataSource] C. jdbc:db2://[DataSource]/[UserID]: [Password] D. jdbc:db2://[ServerID]:[Port]/
Question 16
Question 17
Question 18
[DataSource] E. db2.jdbc://[ServerID]:[Port]/ [DataSource] Which of the following JDBC interfaces contains the methods commit() and rollback()? A. Statement B. ResultSet C. Connection D. DriverManager What is the maximum number of global transactions that can be associated with a DB2XAData-Source object at any one time? A. 1 B. 255 C. Determined by the resources available D. Determined by the MAX_TRANSACTIONS object attribute Which of the following JDBC objects can be used to execute SQL statements that contain parameter markers? A. Statement B. CallableStatement C. PreparedStmt D. CallableStmt The correct answer is B. The CallableStatement object is used to execute SQL stored procedures. (This object is derived from the Statement object.) The correct answer is D. JDBC applications and applets are forced to work exclusively with dynamic SQL. SQLJ, however, provides a way for Java developers to embed static SQL statements in Java applications and applets. The correct answer is C. Parameter markers are represented by the question mark (?) character and indicate the position in an SQL statement where a value is to be substituted when the statement is actually executed. This substitution is carried out by calling the appropriate setXXX() method of the PreparedStatement or CallableStatement object the SQL statement is associated with. In this scenario, the setInt() and setString() methods of the PStmt object must be used to provide values for the parameter markers used in the SQL statement "INSERT INTO employees VALUES (?, ?)". The correct answer is A. SQLJ applications rely on objects known as result set iterators (or iterators) to obtain and process the results of a query. A result set iterator is a special interface object that is used to iterate (move back and forth) over the contents of a result data set. Two types of iterators exist: positioned iterators and named iterators. The correct answer is A. The getWarnings() method returns the first warning reported for a Connection object. The code segment shown in Answer A illustrates the proper way to check for warnings after an attempt to establish a data source connection has been made. The correct answers are B and E. The DB2 SQLJ Profile Customizer, which is invoked by executing either the
Answers Question 1
Question 2
Question 3
Question 4
Question 5
Question 6
Question 7
Question 8
db2profc or the db2sqljcustomize command, is used to create a DB2 customization for the serialized profile file specified (which is generated by the SQLJ translator), optionally online-check all SQL statements encountered that can be dynamically prepared, and, by default, create DB2 packages for the application and store them in the appropriate database's system catalog. The correct answer is C. When the DB2 JDBC DataSource interface is used, the following information is provided as object property values: serverName, portNumber, databaseName, user, password, and description. The correct answer is D. A ResultSetMetaData object is used to obtain information about the properties of a result data set that has been produced in response to a query. Some of the more common ResultSetMetaData interface methods used include the following: getColumnCount(). Returns the number of columns found in the corresponding ResultSet object. getColumnName(int). Obtains the name that has been assigned to the designated column. getColumnType(int). Obtains the SQL data type that has been assigned to the designated column. isNullable(int). Specifies whether the designated column contains, accepts, or contains and accepts null values. The correct answer is B. The DB2 Universal JDBC driver provides four implementations of the DataSource interface: DB2SimpleDataSource. This implementation provides the same functionality as the DataSource interface but does not support connection pooling. DB2DataSource. This implementation provides the same functionality as the DataSource interface and provides connection pooling support. (With this implementation, connection pooling is handled internally and is transparent to the application that uses it.) DB2ConnectionPoolDataSource. This implementation provides the same functionality as the DB2DataSource interface; however, with this implementation, you must manage the connection pooling yourself, by either writing your own code or by using a tool such as Web-Sphere Application Server. DB2XADataSource. This implementation provides the same functionality as the DataSource interface along with connection pooling, and it supports distributed transactions (also known as two-phase commits). With this implementation, you must manage the distributed transactions and connection pooling yourself, by either writing your own code or by using a tool such as WebSphere Application Server. In this case, because both connection pooling and distributed transaction support are needed, the DB2XADataSource implementation must be used. The correct answer is D. An SQLException object is used to obtain diagnostic information about what caused a data source-related error or warning condition to occur; the getErrorCode() method of the SQLException class is
Question 9
Question 10
Question 11
Question 12
Question 13
Question 14
Question 15
Question 16
Question 17
Question 18
used to retrieve vendor-specific error code (in our case, DB2specific error code) information generated for an SQLException object. The correct answer is C. A typical SQLJ application or applet is comprised of special statements that are used like any other normal Java statement. Two types of SQLJ statements are available: declaration statements, which are used to create objects that will be used by the application, and executable statements, which are used to embedded static SQL statements in an application. Executable statements appear in a Java source code file in SQLJ clauses, which are distinguished by their formatevery SQLJ clause begins with the token "#sql" and ends with a semicolon (;). The correct answer is D. The DatabaseMetaData object is used to obtain information about the data source to which a JDBC application has established a connection. Two methods for this objectsupportsOpenCursorsAcrossCommit() and supportsOpenCursorsAcrossRollback()are used to determine whether open cursors will persist across transaction boundaries (i.e., after commit and rollback operations are performed). The correct answer is B. A Statement object can be used both to execute an SQL statement and to obtain any results produced by the statement's execution. (The Statement interface has two important sub interfaces: PreparedStatement, which represents an SQL statement that has been prepared for execution (that may or may not contain parameter markers), and CallableStatement, which represents an SQL statement that is to be used to invoke a stored procedure.) The correct answer is A. The DB2 SQLJ Profile Customizer, which is invoked by executing either the db2profc or the db2sqljcustomize command, is used to create DB2 packages for SQLJ applications or applets and store them in the appropriate database's system catalog. The correct answers are A and D. A valid URL for the DB2 UDB "app" JDBC driver is jdbc:db2:[DataSource], and a valid URL for the DB2 UDB "applet" JDBC driver is jdbc:db2://[ServerID]:[Port]/[DataSource]. (Typically, user ID and password information is supplied with a valid URL when the getConnection() method of the DriverManager class is invoked.) The correct answer is C. The commit() and rollback() methods are supplied by the Connection interface to provide support for transaction management. (The commit() method is used to commit the current transaction associated with a Connection object while the rollback() method is used to roll back the current transaction associated with a Connection object.) The correct answer is C. If you are using an XA-compliant transaction manager such as IBM WebSphere, BEA Tuxedo, or Microsoft Transaction Server, the number of transactions that can be running at one time is limited only by the resources available. The correct answer is B. To increase reusability, SQL statements associated with a PreparedStatement or
CallableStatement object can include parameter markers in place of constants, expressions, or both. (Parameter markers are represented by the question mark (?) character and indicate the position in an SQL statement where a value is to be substituted when the statement is actually executed.)
Implementation Parameter Descriptor (IPD) Implementation Row Descriptor (IRD) SQLGetDescRec() SQLGetDescField() SQLSetDescRec() SQLSetDescField() SQLMoreResults() Block cursor SQL_ATTR_ROWSET_SIZE Column-wise binding Row-wise binding Scrollable cursors Static cursor Dynamic cursor Keyset-driven cursor Mixed cursor "Type 1" connections Remote unit of work "Type 2" connections Application-directed distributed unit of work Connection states RELEASE Two-phase commit Distributed unit of work Transaction coordinator Resource Manager Transaction Manager database Indoubt transaction Database consistency Inconsistent Interleaved Transactions Serializable Transactions Concurrency Lost Updates Dirty Reads Non-repeatable Reads Phantoms Isolation Levels Repeatable Read Read Stability Cursor Stability Uncommitted Read SQL_ATTR_TXN_ISOLATION setTransactionIsolation() Locks LOCK TABLE Techniques you will master: Understanding the difference between static SQL and dynamic SQL, as well as knowing how to identify when each is used in a source code file. Knowing how to create atomic and not-atomic compound SQL statements, as well as knowing the difference between the two. Knowing how parameter markers are used in embedded SQL, CLI/ODBC, and JDBC applications. Understanding the difference between Type 1 and Type 2 connections, as well as knowing how to manage transactions across multiple databases when Type 2 connections are used.
Knowing how two-phase commit processing works. Understanding how activities performed by transactions are isolated from one another in a multi-user environment. Understanding how DB2 UDB provides concurrency control with isolation levels and locks. Recognizing the types of isolation levels available and understanding when each is to be used. Understanding how locks are acquired.
Static SQL
A static SQL statement can be hard-coded in an application program at development time because information about its structure and the objects it references (i.e., tables, columns, and data types) is known in advance. Because the details of a static SQL statement are known at development time, the work of analyzing the statement and selecting the optimum data access plan to use to execute the statement is performed as part of the development process (during precompiling or bindingif deferred binding is used). As a result, static SQL statements execute quickly because their operational form is stored in the database (as a package) and does not have to be generated at application run time. The downside to this is that all static SQL statements must be prepared (their access plans must be generated and stored in the database) before they can be executed, and the SQL statements themselves cannot be altered at application run time. In addition, because static SQL applications require prior knowledge of database objects, changes made to these objects after the application is developed can produce undesirable results. The following are examples of embedded static SQL statements: EXEC SQL INSERT INTO TABLE1 (COL1) VALUES (:Value1);
EXEC SQL SELECT COL1 FROM TABLE1 INTO :Value1; EXEC SQL DECLARE CURSOR CUR1 FOR SELECT (*) FROM TABLE1; Generally, static SQL statements are well suited for high-performance applications that execute predefined operations against a known set of database objects.
Dynamic SQL
Although static SQL statements are relatively easy to incorporate into an application, their use is somewhat limited because their format must be known in advance. Dynamic SQL statements, on the other hand are much more flexible because they can be constructed at application run time; information about a dynamic SQL statement's structure and the objects it intends to interact with does not have to be known at development time. Furthermore, because dynamic SQL statements do not have a precoded, fixed format, the data object(s) they reference can change each time the statement is executed. Even though dynamic SQL statements are generally more flexible than static SQL statements, they are usually more complicated to incorporate into an application. And because the work of analyzing the statement to select the best data access plan to use is performed at application run time, dynamic SQL statements can take longer to execute than their equivalent static SQL counterparts. (Because dynamic SQL statements can take advantage of the database statistics available at application run time, there can be some cases in which a dynamic SQL statement will execute faster than an equivalent static SQL statement, but those are the exception and not the norm.) When static SQL statements are embedded in an application, they are executed as they are encountered. However, when dynamic SQL statements are used, there are two ways in which they can be processed: Prepare and Execute. This approach separates the preparation of the SQL statement from its execution and is typically used when an SQL statement is to be executed repeatedly. This method is also used when an application needs advance information about the columns that will exist in the result data set produced when a SELECT SQL statement is executed. The SQL statements PREPARE and EXECUTE are used to process dynamic SQL statements in this manner. Execute Immediately. This approach combines the preparation and execution of an SQL statement into a single step and is typically used when an SQL statement is to be executed only once. This method is also used when the application does not need additional information about the result data set that will be produced, if any, when the SQL statement is executed. The SQL statement EXECUTE IMMEDIATE is used to process dynamic SQL statements in this manner. Dynamic SQL statements that are prepared and executed (using either method) at run time are not allowed to contain references to host variables. They can, however, contain parameter markers in place of constants and/or expressions. Parameter markers are represented by the question mark (?) character and indicate the position in the SQL statement where the current value of one or more host variables or elements of an SQLDA data structure variable are to be substituted when the statement is actually executed. (Parameter markers are typically used where a host variable would be referenced if the SQL statement being executed were static.) The following are examples of embedded dynamic SQL statements: EXEC SQL DESCRIBE Stmt INTO :MySQLDA; EXEC SQL PREPARE Stmt FROM :SQLStmt;
EXEC SQL EXECUTE Stmt USING :Value1, :Value2 :Value3; Generally, dynamic SQL statements are well suited for applications that interact with a rapidlychanging database or that allow users to define and execute ad-hoc queries. Many commercial off-the-shelf application providers (such as ERP/CRM/SCM vendors) use dynamic SQL because of its flexibility.
Host Variables
Earlier, we saw that the DB2 Database Manager relies on host variables to move data between an application and a database. We also saw that, to distinguish host variables from other highlevel programming language variables, host variables must be defined in a special section known as a declare section. The beginning of a declare section, you may recall, is defined by the BEGIN DECLARE SECTION SQL statement, while the end is defined by the END DECLARE SECTION statement. Thus, a typical declare section in a C/C++ source code file might look something like this: EXEC SQL BEGIN DECLARE SECTION char EmployeeID[7]; double Salary; EXEC SQL END DECLARE SECTION A declare section can be coded anywhere that high-level programming language variable declarations can be coded in a source code file. And although a source code file typically contains only one declare section, multiple declare sections are allowed. Host variables that are used to transfer data to a database are known as input host variables; host variables that receive data from a database are known as output host variables. Regardless of whether a host variable is used for input or output, its attributes must be appropriate for the context in which it is used. Thus, you must define host variables in such a way that their data types and lengths are compatible with the data types and lengths of the columns they are intended to work withwhen deciding on the appropriate data type to assign to a host variable, you should obtain information about the column or special register the variable will be associated with and refer to the conversion charts found in the IBM DB2 Universal Database Application Development Guide Programming Client Applications documentation. Note A special tool known as the Declaration Generator can be used to generate host variable declarations for the columns of a given table in a database; this tool creates embedded SQL declaration source code files, which can easily be inserted into C/C//, Java, COBOL, and FORTRAN applications. For more information about this utility, refer to the db2dclgen command in the DB2 UDB Command Reference product documentation. Because the Declaration Generator automatically generates declaration sections based on actual database statistics contained in the system catalog, its use eliminates typographical errors and data type mismatch errors from occurring, so it should be used whenever possible. It is important to keep in mind that each host variable used in a source code file must be assigned a unique name; duplicate names are not allowed in the same source code file, even when the host variables are defined in different declare sections. So once a host variable has been created, how is it used to move data between an application and a database? The easiest way to answer this question is to examine a simple embedded SQL source code fragment where host variables are used. The following pseudo-source code, written in the C programming language, shows one example of how host variables can be defined and used: ...
// Define The SQL Host Variables Needed EXEC SQL BEGIN DECLARE SECTION; char EmployeeNo[7]; char LastName[16]; EXEC SQL END DECLARE SECTION; ... // Declare A Static Cursor EXEC SQL DECLARE C1 CURSOR FOR SELECT EMPNO, LASTNAME FROM EMPLOYEE; // Open The Cursor EXEC SQL OPEN C1; // If The Cursor Was Opened Successfully, Retrieve The // Results Until The End Of The Result Data Set Is Reached while (sqlca.sqlcode == SQL_RC_OK) { // Retrieve The Current Record From The Cursor EXEC SQL FETCH C1 INTO :EmployeeNo, :LastName; // Do Something With The Results ... } // Close The Open Cursor EXEC SQL CLOSE C1; ...
Indicator Variables
By default, columns in a DB2 UDB database table can contain null values, and because null values are not stored the same way conventional data is stored, special provisions must be made if an application intends to work with null data. Null values cannot be retrieved and copied to host variables in the same manner that other data values can. Instead, a special flag must be examined to determine whether a specific value is meant to be null. And to obtain the value of this flag, a special variable known as an indicator variable (or null indicator variable) must be associated with the host variable that has been assigned to a "nullable" column. Because indicator variables must be accessible by both the DB2 Database Manager and the application program, they must be defined inside a declare section and they must be assigned a data type that is compatible with the DB2 UDB SMALLINT data type. Thus, the code used to define a null indicator variable in a C/C++ source code file would typically look something like this: EXEC SQL BEGIN DECLARE SECTION
short SalaryNullIndicator; EXEC SQL END DECLARE SECTION Once an indicator variable has been associated with a host variable (a null indicator variable is associated with a specific host variable when it follows a host variable immediately after the host variable is used in an SQL statement), it can be examined as soon as its corresponding host variable has been populated. If an indicator variable contains a negative value, it means that a null value was found and the value of the corresponding host variable should be ignored. Otherwise, the value of the corresponding host variable is valid. Indicator variables can also be used to send null values to a database when an insert or update operation is performed; when processing INSERT and UPDATE SQL statements, the DB2 Database Manager examines the value of any indicator variable provided first. If the indicator variable contains a negative value, the DB2 Database Manager assigns a null value to the appropriate columnprovided null values are allowed. (If the indicator variable is set to zero or contains a positive number or if no indicator value is used, the DB2 Database Manager assigns the value stored in the corresponding host variable to the appropriate column instead.) Thus, the code used in a C/C++ source code file to assign a null value to a column in a table would look something like this: ValueInd = -1; EXEC SQL INSERT INTO TAB1 VALUES (:Value :ValueInd); Again, to understand how indicator variables are used, it helps to look at an example embedded SQL source code fragment. The following pseudo-source code, written in the C programming language, shows one example of how indicator variables are both defined and used: ... // Define The SQL Host Variables Needed EXEC SQL BEGIN DECLARE SECTION; char EmployeeNo[7]; double Salary; short SalaryNI; // Salary - Used If SalaryNI Is // Salary NULL Indicator - Used // Not Null // To Determine If Salary // Value Should Be NULL EXEC SQL END DECLARE SECTION; ... // Declare A Static Cursor EXEC SQL DECLARE C1 CURSOR FOR SELECT EMPNO, DOUBLE(SALARY) FROM EMPLOYEE; // Open The Cursor EXEC SQL OPEN C1; // If The Cursor Was Opened Successfully, Retrieve And // Display All Records Available
while (sqlca.sqlcode == SQL_RC_OK) { // Retrieve The Current Record From The Cursor EXEC SQL FETCH C1 INTO :EmployeeNo, :Salary :SalaryNI; // If The Salary Value For The Record Is NULL, ... if (SalaryNI < 0) { printf("No salary information is available for "); printf("employee %s\n", EmployeeNo); } } // Close The Open Cursor EXEC SQL CLOSE C1; ...
Compound SQL
Earlier, it was mentioned that the DB2 Database Manager, rather than the operating system, is responsible for executing SQL statements. To reduce DB2 Database Manager overhead, several individual SQL statements can be grouped together into a single executable block of code known as a compound SQL statement (or simply compound SQL). In addition to reducing DB2 Database Manager overhead, compound SQL reduces the number of requests that have to be transmitted across the network for remote clients. Compound SQL statements can be processed in one of two ways: Atomic. When a compound SQL statement is processed in this manner, the application executing the statement receives a response from the DB2 Database Manager when all substatements within the compound statement have completed successfully or when one of the sub-statements ends in an error. (If one substatement ends in an error, the entire group of statements is considered to have ended in error, and any changes made to the database by other substatements within the compound statement are backed out with a rollback operation.) Not Atomic. When a compound SQL statement is processed in this manner, the application executing the statement receives a response from the DB2 Database Manager when all substatements within the block have completedall substatements within the compound statement are executed, even if one or more substatements end in error. (If one or more substatements end in error, changes made to the database by those substatements are rolled back; changes made by substatements within the compound statement that executed successfully can only be backed out by explicitly rolling back the transactionvia a rollback operationthat the compound SQL statement was executed from.) The beginning of a compound SQL statement block is defined by the BEGIN COMPOUND SQL statement, and the end is defined by the keywords "END COMPOUND." Thus, a simple atomic compound SQL statement in a C/C++ source code file might look something like this: EXEC SQL BEGIN COMPOUND ATOMIC STATIC UPDATE ACCOUNTS SET ABALANCE = ABALANCE + :delta WHERE AID = :aid; UPDATE TELLERS SET TBALANCE = TBALANCE + :delta
WHERE TID = :tid; INSERT INTO TELLERS (TID, BID, TBALANCE) VALUES (:i, :branch_id, 0); COMMIT; END COMPOUND; As you can see from this example, each substatement coded in a compound SQL statement block does not have to begin with the keywords "EXEC SQL"; however, each substatement does have to be terminated with a semicolon. That's because embedded compound SQL statement blocks are treated as a single SQL statement by the SQL precompiler. Compound statements are typically used for short operations that require little control flow logic but significant data flow. For larger constructs with nested complex control flow, stored procedures are a better choice.
... // Define The SQL Host Variables Needed EXEC SQL BEGIN DECLARE SECTION; char SQLStmt[80]; char EmpNo[7]; short EmpNoNI; int Rating; short RatingNI; EXEC SQL END DECLARE SECTION; ... // Define A Dynamic CALL SQL Statement That Uses // Parameter Markers strcpy(SQLStmt, "CALL StoredProc(?,?)"); // Populate The Host Variables That Will Be Used In // Place Of The Parameter Markers strcpy(JobType, "MANAGER"); // Prepare The CALL SQL Statement EXEC SQL PREPARE SQL_STMT FROM :SQLStmt; // Execute The CALL SQL Statement EXEC SQL EXECUTE SQL_STMT USING :EmpNo:EmpNoNI, :Rating:RatingNI; ...
The parameter type (input, output, or input/output). Except for parameter markers used in stored procedure calls, all parameters are treated as input parameters. The C data type, memory address, and size (length) in bytes of the application variable being bound to the parameter marker. The CLI/ODBC driver used must be able to convert the data from the C data type specified to the SQL data type used by the data source or an error will occur. The SQL data type, precision, and scale of the parameter itself. Optionally, the memory address of a length/indicator variable. This variable is used to provide the byte length of binary or character data, specify that the data is a NULL value, or specify that the data is long and will be sent in pieces using the SQLPutData() function.
Parameter markers can be bound to application variables in any order and once bound, the association with that variable remains in effect until it is overridden or until the corresponding SQL statement handle is freed. To bind a parameter marker to a different variable, an application simply rebinds the parameter marker with the new variable; the previous binding is automatically released. However, if a parameter marker is rebound after an SQL statement has been executed, the new binding does not take effect until the SQL statement is re-executed. To set the value of an application variable that has been (or will be) bound to a parameter marker, an application simply assigns a value to the variable. It is not important when values are assigned to such application variables as long the assignment is made sometime before the SQL statement to which they have been bound is executed. Thus, an application can assign a value to a variable before or after it is bound to a parameter marker, and the value assigned can be changed any number of times. Each time an SQL statement containing parameter markers is executed, the appropriate CLI/ODBC driver simply retrieves the current value of each variable bound to the statement and sends it, along with the SQL statement used to the data source for processing. Length/indicator variables can also be bound to parameter markers when application variables are bound; if a length/indicator variable is bound to a specific parameter, it must be set to one of the following values before the SQL statement is executed: The actual length, in bytes, of the data value stored in the bound application variable. The driver checks this length only if the application variable contains character or binary data. SQL_NTS. (A null-terminated string is stored in the bound application variable). SQL_NULL_DATA. (A null value is stored in the bound application variable. In this case, the driver ignores the value of the bound variable.) SQL_DATA_AT_EXEC or the result of the SQL_LEN_DATA_AT_EXEC (Length) macro. (The value stored in the bound application variable is to be sent to the data source by the SQLPutData() function.) Again, to understand how values are provided for parameter markers in a CLI/ODBC application, it helps to look at an example. The following pseudo-source code, written in the C programming language, shows how values are provided for parameter markers that have been coded in a SELECT SQL statement: ... // Allocate An SQL Statement Handle SQLAllocHandle(SQL_HANDLE_STMT, ConHandle, &StmtHandle); // Define A SELECT SQL Statement That Uses A Parameter // Marker
strcpy((char *) SQLStmt, "SELECT EMPNO, LASTNAME FROM "); strcat((char *) SQLStmt, "EMPLOYEE WHERE JOB = ?"); // Prepare The SQL Statement RetCode = SQLPrepare(StmtHandle, SQLStmt, SQL_NTS); // Bind The Parameter Marker Used In The SQL Statement To // An Application Variable RetCode = SQLBindParameter(StmtHandle, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, sizeof(JobType), 0, JobType, sizeof(JobType), NULL); // Populate The "Bound" Application Variable strcpy((char *) JobType, "DESIGNER"); // Execute The SQL Statement RetCode = SQLExecute(StmtHandle); ... Changing parameter bindings and values with offsets. CLI/ODBC applications have the advantage of being able to specify an offset value that is to be added to a bound parameter's address and to any corresponding length/indicator buffer address when the SQLExecute() or SQLExecDirect() function is used to execute an SQL statement containing parameter markers. (Offsets are specified by calling the SQLSetStmtAttr() function and passing it the SQL_ATTR_PARAM_BIND_OFFSET_PTR value and a pointer to a location in memory that contains an offset value.) This feature allows an application to change parameter bindings and values without having to rebind previously bound application variables. When offsets are used, the original bindings represent a template of how the application buffers are laid outthe application can move this template to different areas of memory by simply changing the offset. New offsets can be specified at any time; each time an offset is specified, it is added to the originally bound buffer address. Thus, offset additions are not cumulative; instead, each offset specified cancels out the last offset used. It goes without saying that the sum of the original bound address and any offset provided must always represent a valid address (the offset and/or the address to which the offset is added can be invalid, provided the sum of the two constitutes a valid address).
Once again, to better understand how values are provided for parameter markers used in a JDBC application, it helps to look at an example. The following Java pseudo-source code illustrates how values are provided for parameter markers that have been coded in an SELECT SQL statement: ... // Declare The Local Memory Variables String ResultSet ... // Define A SELECT SQL Statement That Uses A Parameter // Marker SQLStmt = "SELECT * FROM EMPLOYEE WHERE WORKDEPT = ?"; // Prepare The SQL Statement PStmtObject = ConHandle.prepareStatement(SQLStmt); // Assign A Value To The Parameter Marker Used PStmtObject.setString(1, "D11"); // Execute The SQL Statement Results = PStmtObject.executeQuery(); SQLStmt; Results; PreparedStatement PStmtObject;
that are returned by a stored procedure before any specified data conversion is performed (dynamic output parameters). A single row of data in a result data set: The IRD contains the current row from the data source. (These buffers conceptually contain data as written to, or read from, the data source. However, the stored form of data source data is not specified. Therefore, the data in an IRD could have been converted from its original form.) The ARD contains the current row of data as presented to the application after any specified data conversion has been applied. Although these four descriptors are typically used in the manner described, each can serve in a different capacity. For example, a row descriptor in one statement can serve as a parameter descriptor in another. For both parameter and row descriptors, if the application specifies different data types in corresponding records of the implementation and application descriptor used, the CLI/ODBC driver automatically performs any data conversion necessary when it interacts with the descriptor.
The SQLSetDescField() function can also be used to define the initial (default) values that are used to populate record fields when an application row descriptor is first allocated. To provide a standard method for presenting database data to an application, the initial value of an explicitly allocated descriptor's SQL_DESC_TYPE field is always SQL_DEFAULT. An application may change this at any time by setting one or more fields of the descriptor record. The concept of a default value is not valid for IRD fields. In fact, the only time an application can gain access to IRD fields is when a prepared or executed SQL statement is associated with it.
to execute the batch may return the value SQL_SUCCESS_WITH_INFO or SQL_SUCCESS, depending upon the driver's implementation of batch processing. In all cases, diagnostic information can be retrieved by calling the SQLGetDiagRec() or SQLGetDiagField() function; however, it is unlikely that the information returned by either of these two functions will identify the statement that caused the error to occur.
Block Cursors
In the client/server environment, many applications spend a significant amount of time retrieving data from the database. Part of this time is spent bringing the data across the network, and part of it is spent on network overhead (for example, a call made by a driver to request a row of data). Often, the time spent on network overhead can be reduced by using block cursors (also referred to as "fat" cursors), which can return more than one row at a time. The rows returned when data is fetched using a block cursor are known as a rowset. (The actual number of rows returned in a rowset is controlled by the SQL_ATTR_ROWSET_SIZE statement attribute.) Screen-based applications often set the rowset size to match the number of rows that will be displayed on the screen while other applications tend to set the rowset size to match the largest number of rows that the application can reasonably handle.) Just as a traditional forwardonly cursor points to the current row, a block cursor points to the current rowsetwhen a block cursor first returns a rowset, the current row is the first row of that rowset. If an application wants to perform operations that work with a single row, it must indicate which row in the rowset is to be treated as the current row. It is important not to confuse a rowset with a result data set. The result data set is maintained at the data source, but the rowset is maintained in application buffers. Also, although a result data set is fixed, the rowset is notit changes position and contents each time a new set of rows are fetched. Figure 7-1 shows the relationship of a block cursor, a result data set, a rowset, and a current row in a rowset.
Figure 7-1: Components of a CLI/ODBC block cursor. Because block cursors return multiple rows of data, applications that use them must bind an array of variables or LOB locators to each column in the result data set that the cursor is associated with. (Collectively, these arrays are sometimes referred to as rowset buffers.) An application binds columns to arrays in the same manner that it binds columns to other application
variables/LOB locatorsby calling the SQLBindCol() function. The only difference is that the addresses provided as SQLBindCol() function parameters reference arrays instead of individual variables/LOB locators. However, before binding columns to arrays, an application must decide on the binding style that it will use. The following binding styles are available: Column-wise binding. One or more arrays are bound to each column in the result data set for which data is to be returned. This is called column-wise binding because each array (or set of arrays) is associated with a single column in the result data set. Row-wise binding. A data structure that holds a single data value for each column in a row is defined, and each element of the first structure in an array of these structures is bound to each column in the result data set for which data is to be returned. This is called row-wise binding because each data structure contains the data for a single row in the result data set. The decision of whether to use column-wise binding or row-wise binding is largely a matter of preference. Column-wise binding is the default binding style used; however, row-wise binding might correspond more closely to an application's data layout, in which case it could provide better performance. Applications can change from column-wise binding to row-wise binding by setting the SQL_ATTR_ROW_BIND_TYPE statement attribute.
Scrollable Cursors
Interactive applications, especially those written for personal computers, often need to provide a way for a user to scroll through data in a result data set using the arrow keys, "Page Up" and "Page Down" keys, or scroll bar and mouse. For such applications, returning to a previously fetched row can be a problem. One solution to this problem is to close and reopen the cursor and then fetch rows until the cursor reaches the required row. Another possibility is to read the result data set once and store it locally to implement scrolling in the application. Both methods only work well with small result data sets, and the latter method can be quite difficult to implement. A better solution is to use a cursor that can move forward and backward in the result data set. A cursor that provides the ability to move forward and backward within a result data set is called a scrollable cursor. The ability to move backward in a result data set raises an important question: Should the cursor detect changes made to rows previously fetched? In other words, should it detect updated, deleted, and newly-inserted rows? This question arises because the definition of a result data set (that is, the set of rows matching certain criteria) does not state when rows are checked to see if they match that criteria, nor does it state whether rows must contain the same data each time they are fetched. The former omission makes it possible for scrol-lable cursors to detect whether rows have been inserted or deleted, and the latter makes it possible for them to detect updated data. To cover the different needs of different applications, CLI/ODBC defines the following types of scrollable cursors: Static Dynamic Keyset-driven Mixed Each of these cursors varies, both in expense and in ability to detect changes made to the underlying result data set. Static cursors detect few or no changes but are relatively cheap to implement; dynamic cursors detect all changes made but can be quite expensive to implement. Keyset-driven and mixed cursors lie somewhere in between, detecting most changes made, at less expense than dynamic cursors.
Static Cursors
With a static cursor, the result data set appears to be static; static cursors typically do not detect changes made to the result data set by other applications after the cursor has been opened. However, static cursors may detect the effects of their own insert, update, and delete operations, although they are not required to do so. This type of cursor is most useful for read-only
applications that do not need the most up-to-date data available or for applications for which multiple users never need to modify data concurrently. Static cursors are commonly implemented by locking the rows in the result data set or by making a copy, or snapshot, of the result data set. Although locking rows is relatively easy, it significantly reduces transaction concurrency. Making a copy or snapshot of a result data set allows greater concurrency and provides the cursor with a way to keep track of its own inserts, updates, and deletes by modifying the copy; however, a copy is more expensive to make and can differ from the underlying data as other applications attempt to alter that data.
Dynamic Cursors
Unlike static cursors, dynamic cursors can detect changes made to the result data set by other applications after the cursor has been opened; dynamic cursors detect the effects of their own insert, update, and delete operations, as well as the effects of similar operations performed by other applications. (This is subject to the isolation level being used by the transaction that has opened the cursor; the isolation level used is controlled by the value of the SQL_ATTR_TXN_ISOLATION connection attribute.)
Keyset-driven Cursors
A keyset-driven cursor lies somewhere between a static cursor and a dynamic cursor in its ability to detect changes. Like static cursors, keyset-driven cursors do not always detect changes made to the set of rows in the result data set or to the order in which rows are returned. Like dynamic cursors, keyset-driven cursors are able to detect changes made to the rows that already reside in the result data set (depending on the transaction isolation level being used). The advantage of using this type of cursor is that it provides access to the most upto-date data values while allowing an application to retrieve (fetch) rows based on absolute position within a result data set. When a keyset-driven cursor is opened, it saves keys (unique row identifiers) for the entire result data set. A key can be a row ID (if available), a unique index value, a unique key, or the entire row. As the cursor scrolls through the result data set, it uses the keys in the keyset to retrieve the current data values for each row. Because of this, keyset-driven cursors always detect their own updates and deletes as well as updates and deletes made by other applications. For example, suppose a keyset-driven cursor retrieves a row of data from a result data set. Now suppose another application then updates or deletes that row. When a keyset-driven cursor attempts to refetch a row that has been deleted, this row appears as a "hole" in the result data set; the key for the row exists in the keyset, but the row no longer exists in the result data set. If the key for a row is updated, the update is treated as if the original row had been deleted and a new row inserted. As a result, these rows also appear as holes in the result data set. If the keyset-driven cursor retrieves that row again, it will see the changes made by the other application because it retrieved the row using its key. Figure 7-2 identifies the basic components of a keyset-driven cursor.
Figure 7-2: Components of a CLI/ODBC keyset-driven cursor. A keyset-driven cursor can always detect rows deleted by others and can optionally remove the keys for rows that it deletes itself from the keyset, thereby hiding its own deletes. Rows inserted by other applications are never visible to keyset-driven cursors. That's because no keys for these rows exist in the keyset. However, a keyset-driven cursor can optionally add keys
to the keyset for rows that it inserts itself. (Keyset-driven cursors that do this can detect their own inserts.) Keyset-driven cursors are commonly implemented by creating a temporary table that contains keys and row versioning information for each row in the result data set. To scroll through the original result data set, the keyset-driven cursor opens a static cursor over the temporary table; to retrieve a row in the original result data set, the keyset-driven cursor first retrieves the appropriate key from the temporary table and then retrieves the current values for the row. If block cursors are used, the cursor must retrieve multiple keys and rows.
Mixed Cursors
A mixed cursor is a combination of a keyset-driven cursor and a dynamic cursor. Mixed cursors are used when the result data set is too large to reasonably save keys for and are implemented by creating a keyset that is smaller than the entire result data set but larger than the rowset used. As long as the application scrolls within the keyset, the behavior is the same as a keyset-driven cursor. When the application scrolls beyond the keyset, the behavior becomes dynamicthe cursor fetches the requested rows and creates a new keyset for them. After the new keyset is created, the behavior reverts to keyset-driven within the new keyset. For example, suppose a result set has 1,000 rows and uses a mixed cursor with a keyset size of 100 and a rowset size of 10. When the first rowset is fetched, the cursor creates a keyset consisting of keys for the first 100 rows. It then returns the first 10 rows, as requested. Now suppose another application deletes rows 11 and 101. If the cursor attempts to retrieve row 11, it will encounter a hole because it has a key for this row but no row exists; this is keyset-driven behavior. If the cursor attempts to retrieve row 101, the cursor will not detect that the row is missing because it does not have a key for the row. Instead, it will retrieve what was previously row 102. This is dynamic cursor behavior. A mixed cursor is equivalent to a keyset-driven cursor when the keyset size is equal to the result data set size. A mixed cursor is equivalent to a dynamic cursor when the keyset size is equal to 1.
before an application using any of these interfaces can perform operations against a database, it must first establish a connection to that database. With embedded SQL applications, database connections are made (and in some cases, terminated) by executing the CONNECT SQL statement. With CLI/ODBC applications, database connections are established by calling the SQLConnect(), SQLDriverConnect(), and/or the SQLBrowseConnect() function. And with JDBC and SQLJ applications, database connections are made by invoking the getConnection() method of a DriverManager object. During the connection process, the information needed to establish a connection (such as authorization ID and corresponding password of an authorized user) is passed to the database specified for validation. Embedded SQL applications have the option of using two different types of connection semantics. These two types, known simply as "Type 1" and "Type 2," support two types of transaction behavior. Type 1 connections support only one database connection per transaction (referred to as remote unit of work) while Type 2 connections support any number of database connections per transaction (referred to as application-directed distributed unit of work). Essentially, when Type 1 connections are used, an application can be connected to only one database at a time; once a connection to a database is established and a transaction is started, that transaction must be either committed or rolled back before another database connection can be established. On the other hand, when Type 2 connections are used, an application can be connected to several different databases at the same time, and each database connection will have its own transactions. The type of connection semantics an application will use is determined by the value assigned to an SQL precompiler option at the time the application is precompiled. (CLI/ODBC, JDBC, and SQLJ applications can connect to any number of data sources at any timeno special connection semantics are required.) When Type 2 connections are used, each time the CONNECT statement is executed, any database connection that existed before the CONNECT statement was executed is placed in the "Dormant" state; the new database server name is added to the list of available servers; and the new connection is placed into both the "Current" state and the "Held" state. (Initially, all database connections are placed in the "Held" state, which means that the connection will not be terminated the next time a commit operation is performed.) When the RELEASE SQL statement is executed, a connection is removed from the "Held" state and placed in the "Release-Pending" state, which means that the connection will be terminated by the next successful commit operation (roll back operations have no effect on connections). Regardless of whether a connection is in the "Held" or "Release-Pending" state, it can also be in the "Current" or "Dormant" state. When a connection is in the "Current" state, SQL statements executed by the application can reference data objects that are managed by the corresponding database server. (You can find out which connection is in the "Current" state simply by examining the value of the CURRENT SERVER special register.) When a connection is in the "Dormant" state, however, it is no longer current, and no SQL statement is allowed to reference its data objects. Either the SET CONNECTION SQL statement or the CONNECT RESET statement can be used to change the state of a specific connection from the "Dormant" state to the "Current" state, which automatically places all other existing connections in the "Dormant" state. (Only one connection can be in the "Current" state at any given point in time.)
statement executes successfully or discarded if the statement fails) or explicitly terminated by executing the COMMIT or the ROLLBACK SQL statement. So what happens when an application establishes connections with several databases at the same time? In this case, each data source connection can constitute a separate transaction boundary. Figure 7-3 illustrates how multiple transaction boundaries can coexist when an application interacts with two separate data sources at the same time.
Figure 7-3: Transaction boundaries in an application that interacts simultaneously with multiple data sources. It is important to remember that commit and rollback operations only have an effect on changes that have been made within the transactions that they terminate. So in order to evaluate the effects of a series of transactions, you must be able to identify where each transaction begins, as well as when and how each transaction is terminated.
How do the transaction coordinator and the Transaction Manager database coordinate transaction processing across multiple database connections? To answer that question, we need to examine the steps that take place during a two-phase commit process. The following list identifies these steps: 1. When the application program starts a transaction, it automatically connects to the Transaction Manager database. 2. Just before the first SQL statement in the transaction is executed, the transaction coordinator sends a Transaction Register (XREG) request to the Transaction Manager database to register the new transaction. 3. The Transaction Manager database responds to the application program by providing a unique global transaction ID for the new transaction (because the XREG request was sent without a predefined ID). 4. After receiving the transaction ID, the application program registers the new transaction (using the transaction ID) with the database containing the required user data. A response is sent back to the application program when the transaction has been successfully registered. 5. SQL statements issued against the database containing the user data are handled in the normal manner with the return code for each SQL statement processed being returned in an SQLCA data structure variable. 6. Steps 4 and 5 are repeated for each database accessed by the transaction. All other databases accessed in the transaction receive the global transaction ID just before the first SQL statement is executed against them. As we saw earlier, the SET CONNECTION SQL statement is used to switch between database connections. 7. When the application program requests that the current transaction be committed, the transaction coordinator sends a "PREPARE" message to all databases that have been accessed by the transaction. Each database that receives this message writes a "PREPARED" record to its log files and sends a response back to the transaction coordinator. 8. When the transaction coordinator receives a positive response from all databases that the "PREPARE" message was sent to, it sends a message to the Transaction Manager database to inform it that the transaction has been prepared and is now ready to be committed. This completes the first phase of the two-phase commit process. 9. The Transaction Manager database writes a "PREPARED" record to its log file and sends a message back to the transaction coordinator, informing it that the second phase of the commit process can be started. The transaction coordinator then forwards this message to the application program. 10. When the transaction coordinator receives the message to begin the second phase of the commit process, it sends a "COMMIT" message to all databases that the "PREPARE" message was sent to (telling them to commit all changes made by the transaction). Each database that receives this message writes a "COMMITTED" record to its log file and releases all locks that were held by the transaction. When each database has completed committing its changes, it sends a reply back to the transaction coordinator. 11. When the transaction coordinator receives a positive response from all databases that the "COMMIT" message was sent to, it sends a message to the Transaction Manager database to inform it that the transaction has been completed. The Transaction Manager database then writes a "COMMITTED" record to its log file to indicate that the transaction is complete and sends a message to the transaction coordinator to indicate that it has finished processing. The transaction coordinator then forwards this message to the application program to indicate that the transaction has been terminated. Figure 7-4 illustrates the sequence of events that are executed to support two-phase commit processing. (Each numbered item corresponds to one of the steps just outlined.)
1. Connecting to the databases that replied that they were "PREPARED" to commit during the first phase of the commit process. 2. Attempting to commit the indoubt transactions at that database. (If no indoubt transactions can be found, the DB2 Database Manager assumes that the database successfully committed the transaction during the second phase of the commit process.) 3. Committing the indoubt transactions in the Transaction Manager database after all indoubt transactions have been committed in the participating databases. Other database errors. If one of the databases accessed in the transaction fails and is restarted, the DB2 Database Manager for that database will check the log files of the Transaction Manager database to determine whether the transaction should be rolled back or committed. If the transaction is not found in the Transaction Manager database log files, the DB2 Database Manager assumes the transaction was rolled back, and the indoubt transactions for this database will be rolled back as well. Otherwise, the database will wait for a commit request from the transaction coordinator. If, for some reason, you cannot wait for the transaction coordinator to automatically resolve indoubt transactions, you can manually resolve them by "making a heuristic decision" and applying it to all applicable records. In order to apply a heuristic decision to an indoubt transaction, you must first acquire its global transaction ID. The LIST INDOUBT TRANSACTIONS command or the List Indoubt Transactions API can be used to obtain the global transaction ID for all indoubt transactions that exist for a specified database; once an indoubt transaction's transaction ID has been obtained, the Commit An Indoubt Transaction API can be used to heuristically commit it and the Rollback An Indoubt Transaction API can be used to heuristically roll it back. After a transaction has been heuristically committed or rolled back, the Forget Transaction Status API can be used to tell the database manager to "forget" it by removing all log records that refer to it and releasing its log space. (If the LIST INDOUBT TRANSACTIONS command is executed with the WITH PROMPTING option specified, it can also be used to heuristically roll back, commit, or forget indoubt transactions.) Caution The indoubt transaction processing command and APIs should be used with extreme caution and only as a last resort. The best way to resolve indoubt transactions is to wait for the transaction coordinator to drive the resynchronization process.
can also occur when several users attempt to access the same data at the same time. For example, using the hardware store scenario we just looked at, one user might query the database and discover that no more hammers are available (when there really are) because the query read another user's changes before all tables affected by those changes had been properly updated. Reacting to this information, the user (or an Automated Purchase Ordering System) might then place an order for more hammers when none are needed. To ensure that users and applications accessing the same data at the same time do not inadvertently place that data in an inconsistent state, DB2 UDB relies on two mechanisms known as isolation levels and locks.
Isolation Levels
Earlier, we saw that a transaction (otherwise known as a unit of work) is a recoverable sequence of one or more SQL operations grouped together as a single unit, usually within an application process. A given transaction can perform any number of SQL operationsfrom a single operation to many hundreds or even thousandsdepending on what is considered a "single step" within your business logic. The initiation and termination of a single transaction defines points of data consistency within a database; either the effects of all operations performed within a transaction are applied to the database and made permanent (committed), or the effects of all operations performed are backed out (rolled back) and the database is returned to the state it was in before the transaction was initiated. In single-user, single-application environments, each transaction runs serially and does not have to contend with interference from other transactions. However, in multi-user environments, transactions can execute simultaneously, and each transaction has the potential to interfere with any other transaction that has been initiated but not yet terminated. Transactions that have the potential of interfering with one another are said to be interleaved, or parallel, and transactions that run isolated from each other are said to be serializable, which means that the results of running them simultaneously will be no different from the results of running them one right after another (serially). Ideally, every transaction should be serializable. Why is it important that transactions be serializable? Consider the following: Suppose a travel agent is entering hotel reservation information into a database system at the same time a hotel manager is checking room availability for a conference planning committee. Now, suppose the travel agent blocks off two hundred rooms for a large tour group (to check availability and get a price quote) but does not commit the entry. While the travel agent is relaying the price quote information to the tour group coordinator, the hotel manager queries the database to see how many rooms are available, discovers that all but twenty rooms have been reserved, and tells the conference planning committee that he cannot accommodate their needs. Now, suppose the tour coordinator decides not to reserve the rooms because the quoted price is higher than anticipated. The travel agent rolls back the transaction because no reservations were made, and the two hundred rooms that had been marked as reserved are now shown as being available. Unfortunately, the damage has already been done. The hotel missed the opportunity to host a conference, and they have two hundred vacant rooms they need to fill. If the travel agent's transaction and the hotel manager's transaction had been isolated from each other (serialized), this problem would not have occurred. Either the travel agent's transaction would have finished before the hotel manager's transaction started, or the hotel manager's transaction would have finished before the travel agent's transaction started; in either case, the hotel would not have missed out on the opportunity to host the conference. When transactions are not isolated from each other in multi-user environments, the following types of events (or phenomena) can occur: Lost Updates. This event occurs when two transactions read the same data, both attempt to update that data, and one of the updates is lost. For example: Transaction A and Transaction B read the same row of data and calculate new values for that row based on the original values read. If Transaction A updates the row with its new value and Transaction B then updates the same row, the update operation performed by Transaction A is lost.
Dirty Reads. This event occurs when a transaction reads data that has not yet been committed. For example: Transaction A changes a row of data, and Transaction B reads the changed row before Transaction A commits the change. If Transaction A rolls back the change, Transaction B will have read data that theoretically never existed. Nonrepeatable Reads. This event occurs when a transaction reads the same row of data twice but gets different results each time. For example: Transaction A reads a row of data, and then Transaction B modifies or deletes that row and commits the change. When Transaction A attempts to reread the row, it will retrieve different data values (if the row was updated) or discover that the row no longer exists (if the row was deleted). Phantoms (or Phantom Reads). This event occurs when a row of data matches some search criteria but initially is not seen. For example: Transaction A retrieves a set of rows that satisfy some search criteria, and then Transaction B inserts a new row that contains matching search criteria for Transaction A's query. If Transaction A re-executes the query that produced the original set of rows, a different set of rows will be retrievedthe new row added by Transaction B will now be included in the set of rows returned. Because several different users can access and modify data stored in a DB2 UDB database at the same time, the DB2 Database Manager must be able to allow users to make necessary changes while ensuring that data integrity is never compromised. The sharing of resources by multiple interactive users or application programs at the same time is known as concurrency. One of the ways DB2 UDB enforces concurrency is through the use of isolation levels, which determine how data used in one transaction is "isolated from" other transactions. DB2 Universal Database recognizes and supports the following isolation levels: Repeatable Read Read Stability Cursor Stability Uncommitted Read Table 7-1 shows the various phenomena that can occur when each of these isolation levels are used. Table 7-1: DB2 Universal Database's Isolation Levels and the Phenomena That Can Occur When Each Is Used Isolation Level Phenomena Lost Updates Dirty Reads Nonrepeatable Reads Phantoms
No No No
No No No
No No Yes
No Yes Yes
Uncommitted No Yes Yes Yes Read Adapted from Table 1 on Page 56 of the IBM DB2 Administration GuidePerformance manual.
phantoms cannot occur.) In addition, transactions using the Repeatable Read isolation level will not see changes made to other rows by other transactions until those changes have been committed. Transactions using the Repeatable Read isolation level can retrieve the same set of rows multiple times and perform any number of operations on them until terminated by performing either a commit or a rollback operation. However, no other transaction is allowed to perform any insert, update, or delete operation that would affect the set of rows being accessed by the isolating transactionas long as that transaction remains active. To ensure that the data being accessed by a transaction running under the Repeatable Read isolation level is not adversely affected by other transactions, each row referenced by the isolating transactionnot just the rows that are actually retrieved and/or modifiedis locked. Thus, if a transaction scans 1,000 rows to retrieve 10, locks are acquired and held on all 1,000 rows scannednot just on the 10 rows retrieved. Note If an entire table or view is scanned in response to a query when the Repeatable Read isolation level is used, the entire table or all table rows referenced by the view are locked. This greatly reduces concurrency, especially when large tables are used. So how does this isolation level work in a real-world situation? Suppose you own a large hotel and have a Web site that allows individuals to reserve rooms on a first-come, first-served basis. If your hotel reservation application runs under the Repeatable Read isolation level, when a customer retrieves a list of all rooms available for a given range of dates, you will not be able to change the room rate for those rooms during the date range specified, nor will other customers be able to make or cancel reservations that would cause the list to change if it were generated againas long as the transaction that produced the list is active. (However, you can change room rates for any room that was not scanned in response to the first customer's query. Likewise, other customers can make or cancel room reservations for any room that was not scanned in response to the first customer's query.)
How does this isolation level change the way our hotel reservation application works? Now, when a customer retrieves a list of rooms available for a given range of dates, you will be able to change the room rate for any room in the hotel that does not appear on the list, and other customers will be able to cancel room reservations for rooms that had been reserved for the date range specified by the first customer's query. Therefore, if the customer generates the list of available rooms again (before the transaction that submitted the query terminates), the list produced may contain new room rates and/or rooms that were not available the first time the list was generated.
In most cases, transactions using the Uncommitted Read isolation level can read changes made to rows by other transactions before those changes have been committed. However, such transactions can neither see nor access tables, views, or indexes created by other concurrent transactions until those transactions have been terminated. The same applies to existing tables, views, or indexes that have been droppedtransactions using the Uncommitted Read isolation level will learn that these objects no longer exist only when the transaction that dropped them is terminated. There is one exception to this behavior: When a transaction running under the Uncommitted Read isolation level uses an updatable cursor, the transaction will behave as if it is running under the Cursor Stability isolation level, and the constraints of the Cursor Stability isolation level will apply. So how does the Uncommitted Read isolation level affect our hotel reservation application? Now, when a customer retrieves a list of rooms available for a given range of dates, you will be able to change the room rate for any room in the hotel, and other customers will be able to make or cancel reservations over any date range for any room. Furthermore, the list produced for the first customer may contain rooms that other customers have chosen to cancel reservations for, but whose cancellations have not yet been committed to the database.
isolation levels for different transactions within the application that the first approach provides. The isolation level for JDBC and SQLJ applications is set at application run time by calling the setTransactionIsolation() method of the Connection interface. When the isolation level for an application is not explicitly set using one of these methods, the Cursor Stability isolation level is used as the default. This holds true for commands, SQL statements, and scripts executed from the Command Line Processor, as well as for embedded SQL, ODBC/CLI, JDBC, and SQLJ applications. Therefore, it is also possible to specify the isolation level to be used for any transaction that is to be executed by the Command Line Processor. In this case, the isolation level is set by executing the CHANGE ISOLATION command before a connection to a database is established.
Locking
The one thing that all four isolation levels have in common is that they control how data is accessed by concurrent transactions through the use of locks. So just what is a lock? A lock is a mechanism that is used to associate a data resource with a single transaction for the sole purpose of controlling how other transactions interact with that resource while it is associated with the transaction that has it locked. (The transaction that has a data resource associated with it is said to "hold" or "own" the lock.) Essentially, locks in a database environment serve the same purpose as they do in a house or a carthey determine who can and cannot gain access to a particular resourcewhich, in the case of a data resource, is one or more tablespaces, tables, and/or rows. The DB2 Database Manager imposes locks to prohibit "owning" transactions from accessing uncommitted data that has been written by other transactions and to prevent other transactions from making data modifications that might adversely affect the owning transaction. When an owning transaction is terminated (by being committed or rolled back), any changes made to the resource that was locked are made permanent (or removed), and all locks on the resource that had been acquired by the owning transaction are released. Once unlocked, a resource can be locked again and manipulated by another active transaction. Figure 7-5 illustrates the principles of transaction and resource locking.
Figure 7-5: How DB2 Universal Database prevents uncontrolled concurrent access to a resource by using locks
Identifies the name of an existing table for which the level of locking that all transactions are to use when accessing it is to be specified.
For example, if executed, the SQL statement ALTER TABLE EMPLOYEE LOCKSIZE TABLE would force the DB2 Database Manager to acquire table-level locks for every transaction that accesses the table named EMPLOYEE. On the other hand, if the SQL statement ALTER TABLE EMPLOYEE LOCKSIZE ROW was executed, the DB2 Database Manager would attempt to acquire row-level locks (which is the default behavior) for every transaction that accesses the table named EMPLOYEE. But what if you don't want every transaction that works with a particular table to acquire tablelevel locks? What if, instead, you want only one specific transaction to acquire table-level locks, and you want all other transactions to acquire row-level locks when working with that table? In this case, you leave the default locking behavior alone (so row-level locking is used) and use the LOCK TABLE SQL statement to acquire a table-level lock for the appropriate individual transaction. The syntax for the LOCK TABLE statement is: LOCK TABLE [TableName] IN [SHARE | EXCLUSIVE] MODE where: TableName
Identifies the name of an existing table or declared temporary table that is to be locked.
As you can see, the LOCK TABLE statement allows a transaction to acquire a table-level lock on a particular table, in one of two modes: SHARE mode and EXCLUSIVE mode. If a table is locked using SHARE mode, a table-level Share (S) lock is acquired on behalf of the requesting transaction, and other concurrent transactions are allowed to read, but not change, the data stored in the locked table. If a table is locked using EXCLUSIVE mode, however, a table-level Exclusive (X) lock is acquired, and other concurrent transactions can neither access nor modify data stored in the locked table. For example, if executed, the SQL statement LOCK TABLE EMPLOYEE IN SHARE MODE would acquire a table-level Share (S) lock on the EMPLOYEE table on behalf of the current transaction (provided no other transaction holds a lock on this table), and other concurrent transactions would be allowed to read, but not change, the data stored in the table. On the other hand, if the SQL statement LOCK TABLE EMPLOYEE IN EXCLUSIVE MODE
were executed, a table-level Exclusive (X) lock would be acquired, and no other concurrent transaction would be allowed to read or modify data stored in the EMPLOYEE table. Note The LOCK TABLE statement cannot be used to lock system catalog tables or tables that are referenced by nicknames
Practice Questions
Question 1 Which of the following code fragments is considered to be static SQL? A. EXEC SQL EXECUTE IMMEDIATE :hv1 B. EXEC SQL SELECT c1 FROM t1 INTO :hv1 C. EXEC SQL INSERT INTO t1 VALUES (?) D. EXEC SQL DELETE FROM t1 WHERE EMPNO = ? Which of the following applies to atomic compound SQL? A. Can contain operating system commands B. SQL substatements can succeed or fail, regardless of the success or failure of preceding SQL substatements in the same compound SQL statement block. C. If any SQL substatement in the compound SQL statement block fails, no other substatements are executed and effects of the compound SQL statement are rolled back. D. It is the DB2 UDB term for SQL statements that include subselects, CASE statements, declared temporary tables, common table expressions, and recursive SQL. Given the following table: TAB1 EMPID NAME 1 USER1 2 USER2 3 USER3 Users A and B are using the DB2 Command Line Processor with AUTOCOMMIT turned off. They execute the following commands in the order shown: User A: SET CURRENT ISOLATION RS DECLARE c1 CURSOR FOR SELECT empid FROM tab1 OPEN c1 User B: SET CURRENT ISOLATION RS DELETE FROM tab1 WHERE empid = 3 User A: db2 "FETCH c1" What will each user see in their respective
Question 2
Question 3
Question 4
Question 5
Question 6
windows? A. User A: 1; User B: session hangs B. User A: 1; User B: session completes successfully C. User A: session hangs; User B: session hangs D. User A: session hangs; User B: session completes successfully Using the CS isolation level, Application A drops table TAB1 and does not perform a commit operation. Using the UR isolation level, Application B attempts to retrieve data from TAB1. Which of the following will occur? A. Application A will be prompted to commit the current transaction B. Application B will wait until Application A commits or rolls back the transaction C. Application B will be able to access data in TAB1 until Application A commits the transaction D. Application B will receive an error immediately after Application A's delete operation is performed Which of the following affects concurrency by controlling the isolation level that will be used by embedded SQL applications? A. TXNISOLATION B. SQL_ATTR_TXN_ISOLATION C. setTransactionIsolation() D. ISOLATION The following sequence of SQL statements were successfully executed in manual-commit mode: EXEC SQL CONNECT TO dbase2; EXEC SQL CONNECT TO dbase1; INSERT INTO tab1 VALUES (1, 'Red'); SET CONNECTION dbase2; INSERT INTO tab1 VALUES (2, 'White'); SET CONNECTION dbase1; RELEASE dbase2; COMMIT; INSERT INTO tab1 VALUES (3, 'Blue'); ROLLBACK; Which of the following describes the current state? A. One record was added to DBASE1 and the connection to it is open; one record was added to DBASE2 and the connection to it is closed B. Two records were added to DBASE1 and the connection to it is open; one record was added to DBASE2 and the connection to it is closed C. One record was added to DBASE1; one record was added to DBASE2; and the connections to both DBASE1 and DBASE2 are open
Question 7
Question 8
Question 9
Question 10
D. One record was added to DBASE1; one record was added to DBASE2; and the connections to both DBASE1 and DBASE2 are closed Which of the following does NOT correctly illustrate parameter marker usage? A. SELECT c1 FROM t1 WHERE c2 = ? B. INSERT INTO t1 VALUES (?, ?) C. SELECT ? FROM t1 WHERE c2 = c3 D. UPDATE t1 SET c1 = ? WHERE c2 = c3 Which of the following JDBC interfaces has methods that permit the use of parameter markers? A. Connection B. PreparedStatement C. Statement D. ResultSet A CLI/ODBC application executes the following statement: SQLExecDirect(StmtHandle, "SELECT c1 FROM t1; SELECT c1 FROM t2;", SQL_NTS); When all of the records retrieved from table T1 have been processed, which of the following functions must be executed in order to process the records retrieved from table T2? A. SQLFetch() B. SQLNextResultSet() C. SQLCloseCursor() D. SQLMoreResults() Given the following table definition: CREATE TABLE tab1 (c1 SMALLINT, c2 VARCHAR(30)) and the following embedded SQL pseudo-source code: EXEC SQL BEGIN DECLARE SECTION;
EXEC SQL END DECLARE SECTION; ... hv1 = 20; hv2 = -1;
strcpy(hv3, "USER1"); hv4 = 0; strcpy(stmt, "INSERT INTO t1 VALUES (?, ?) EXEC SQL PREPARE stmt FROM :text_stmt; Which of the following statements will successfully insert a row into table T1 where C1 contains a null value and C2 contains the value USER1? A. EXEC SQL EXECUTE stmt USING :hv2, :hv3) B. EXEC SQL EXECUTE stmt USING :hv4 :hv2, :hv3) C. EXEC SQL EXECUTE stmt USING :hv1 hv2, :hv3) D. EXEC SQL EXECUTE stmt USING :hv1 :hv4, :hv3) Given the following code: EXEC SQL BEGIN COMPOUND NOT ATOMIC INSERT INTO territory VALUES (:Country1, :Region); INSERT INTO territory VALUES (:Country2, :Region); INSERT INTO territory VALUES (:Country3, :Region); UPDATE territory SET region = :Region2 WHERE Country = :Country4); INSERT INTO territory VALUES (:Country4, :Region2); COMMIT; END COMPOUND; Assuming that all substatements except the following executed successfully: INSERT INTO territory VALUES (:Country2, :Region); How many rows were added to the TERRITORY table? A. 0 B. 1 C. 2 D. 3 Which of the following can NOT be locked, either implicitly or explicitly? A. Declared temporary tables B. User tables C. System database directory D. Rows A table contains a list of all seats available at a football stadium. A seat consists of a section number, a seat number, and whether or not the seat has been assigned. A ticket agent working at the box office generates a list of all unassigned seats. When the agent refreshes the list, it should only change if another agent assigns one or more unassigned seats. Which of the following is the best isolation level to use for this application?
Question 11
Question 12
Question 13
Question 14
Question 15
Question 16
Question 17
A. Repeatable Read B. Read Stability C. Cursor Stability D. Uncommitted Read Which two of the following are TRUE for a remote unit of work? A. Type 1 connections must be used B. Operations can only be performed against a single data source C. Type 2 connections must be used D. Operations can be performed against any number of data sources E. A transaction coordinator and a Transaction Manager database are used to coordinate COMMITs and ROLLBACKs Which of the following CLI/ODBC functions can be used to obtain information about a specific parameter in an SQL statement after the SQLPrepare() function is called? A. SQLGetInfo() B. SQLGetParamInfo() C. SQLBindParameter() D. SQLGetDescRec() Which of the following CLI/ODBC statement handle attributes is used to control the number of rows that are retrieved from a result data set each time the SQLFetch() function is called? A. SQL_ATTR_MAX_ROWS B. SQL_ATTR_ROW_ARRAY_SIZE C. SQL_ATTR_ROWSET_SIZE D. SQL_ATTR_FETCH_SIZE A developer wants to use static SQL to retrieve a row of data from a table and he is not sure how many rows will be returned by his query. Which of the following code fragments correctly demonstrates the SQL statements needed to meet his objective? A. EXEC SQL SELECT col1 FROM tab1 USING CURSOR c1; EXEC SQL FETCH cur INTO :hv; EXEC SQL CLOSE cur;
B. EXEC SQL DECLARE cur CURSOR FOR SELECT col1 FROM tab1;
EXEC SQL OPEN cur; EXEC SQL FETCH cur INTO :hv; EXEC SQL CLOSE cur;
C. EXEC SQL PREPARE stmt FOR SELECT col1 FROM tab1; EXEC SQL DECLARE cur CURSOR FOR stmt;
EXEC SQL OPEN cur; EXEC SQL FETCH cur INTO :hv; EXEC SQL CLOSE cur
Question 18
D. EXEC SQL SELECT col1 FROM tab1 INTO :hv; Which of the following correctly identifies what indoubt transactions are? A. Transactions that have not yet completed the first phase of the two-phase commit process B. Transactions that were "lost" during the second phase of the two-phase commit process C. Transactions that have completed the first phase, but not the second phase of the two-phase commit process D. Poorly constructed transactions that have failed and left doubt about a database's consistency The correct answer is B. Parameter markers are not used with static SQL statements, so answers C and D are automatically eliminated. The EXECUTE IMMEDIATE statement is used to execute dynamic SQL statements, and the SELECT INTO SQL statement can only be executed statically, so the correct answer is B. The correct answer is C. When a compound SQL statement is processed atomically, the application executing the statement receives a response from the DB2 Database Manager when all substatements within the compound statement have completed successfully or when one of the substatements ends in an error. If one substatement ends in an error, the entire group of statements is considered to have ended in error, and any changes made to the database by other substatements within the compound statement are backed out with a rollback operation. The correct answer is A. Transactions using the Read Stability isolation level can retrieve a set of rows and perform any number of operations on them until terminated by performing either a commit or rollback operation. However, no other transaction is allowed to perform any update or delete operation that would affect the set of rows retrieved by the isolating transactionas long as that transaction exists. Therefore, the first row retrieved from the table named TAB1 will be shown to User A, and the session for User B will hang until User 1 closes cursor C1 and commits (or rolls back) the transaction. The correct answer is B. In most cases, transactions using the Uncommitted Read (UR) isolation level can read changes made to rows by other transactions before those changes have been committed. However, such transactions can neither see nor access tables, views, or indexes created by other
Answers Question 1
Question 2
Question 3
Question 4
Question 5
Question 6
Question 7
Question 8
Question 9
Question 10
concurrent transactions until those transactions have been terminated. The same applies to existing tables, views, or indexes that have been droppedtransactions using the Uncommitted Read isolation level will learn that these objects no longer exist only when the transaction that dropped them is terminated. The correct answer is D. The isolation level for embedded SQL applications written in a supported compiled language is set through the ISOLATION option of the PRECOMPILE and BIND commands. (The isolation level for CLI/ODBC applications is set at application run time by calling the SQLSetConnectAttr() function with the SQL_ATTR_TXN_ISOLATION connection attribute specified. Alternatively, the isolation level for ODBC/CLI applications can be set by assigning a value to the TXNISOLATION keyword in the db2cli.ini configuration file; however, this approach does not provide the flexibility of changing isolation levels for different transactions within the application that the first approach does. The isolation level for JDBC and SQLJ applications is set at application run time by calling the setTransactionIsolation() method of the JDBC Connection interface.) The correct answer is A. When the RELEASE SQL statement is executed, a connection is removed from the "Held" state and placed in the "Release-Pending" state, which means that the connection will be terminated by the next successful commit operation. In this example, one row is added to TAB1 of DBASE1, one row is added to TAB1 of DBASE2, DBASE2 is placed in "Release-Pending" state, the row added to TAB1 of DBASE1 is committed and the connection to DBASE2 is terminated, another row is added to TAB1 of DBASE1, and then that row is removed with a rollback operation. The correct answer is C. Parameter markers are typically used where a host variable would be referenced if the SQL statement being executed were static; neither host variables nor parameter markers can be used to identify column names in a SELECT statement. The correct answer is B. When parameter markers are used in JDBC applications, values are supplied for each parameter marker placed in an SQL statement by calling the appropriate setXXX() method of the PreparedStatement or CallableStatement object that the SQL statement is associated with. The correct answer is D. When two or more SELECT SQL statements are executed together as a batch, multiple result data sets are produced. To process multiple result data sets, an application must call the SQLMoreResults() function. This function discards the contents of the current result data set and makes the next result data set available. The correct answer is B. When processing INSERT and UPDATE SQL statements, the DB2 Database Manager examines the value of any indicator variable provided first, and if it contains a negative value, the DB2 Database Manager assigns a null value to the appropriate columnprovided null values are allowed. If the indicator variable is set to zero or contains a positive number, or if no indicator value is used, the
Question 11
Question 12
Question 13
Question 14
Question 15 Question 16
Question 17
DB2 Database Manager assigns the value stored in the corresponding host variable to the appropriate column instead. The correct answer is D. When a NOT ATOMIC compound SQL statement is processed, the application executing the statement receives a response from the DB2 Database Manager when all substatements within the block have completedall substatements within the compound statement are executed, even if one or more substatements end in error. If one or more substatements end in error, changes made to the database by those substatements are rolled back. Changes made by substatements within the compound statement that executed successfully can only be backed out by rolling back the transaction that the compound SQL statement was executed from. The correct answer is C. The LOCK TABLE statement cannot be used to lock system catalog tables or tables that are referenced by nicknames. The system database directory is a file that is used to keep track of where databases have been created and catalogedtherefore, it cannot be locked. The correct answer is C. If the Repeatable Read isolation level is used, other agents will be unable to assign seats as long as the transaction that generated the list remains active; therefore, the list will not change when it is refreshed. If the Read Stability isolation level is used, other agents will be able to unassign currently assigned seats (and these unassigned seats will show up when the list is refreshed) but will not be able to assign any seat that appears in the list as long as the transaction that generated the list remains active. If the Uncommitted Read isolation level is used, other agents will be able to unassign currently-assigned seats, as well as assign unassigned seats; however, uncommitted seat unassignments or assignments will show up when the list is refreshed, and the agent may make an inappropriate change based on this data. Therefore, the best isolation level to use for this particular application is the Cursor Stability isolation level. The correct answers are A and B. Type 1 connections support only one database connection per transaction (referred to as remote unit of work) while Type 2 connections support any number of database connections per transaction (referred to as application-directed distributed unit of work). Essentially, when Type 1 connections are used, an application can be connected to only one database at a time; once a connection to a database is established and a transaction is started, that transaction must be either committed or rolled back before another database connection can be established. The correct answer is D. The SQLGetDescRec() function can be used to retrieve the contents of several parameter or column record fields, which identify the data type and storage of a specific parameter or column in a result data set. The correct answer is C. The rows returned when data is fetched using a block cursor are known as a rowset, and the actual number of rows that are returned in a rowset is controlled by the SQL_ATTR_ROWSET_SIZE statement attribute. The correct answer is B. When a query that has the potential to generate a result data set containing several rows is executed, a cursor must be used to retrieve data values from
the result data set produced. The following steps must be followed (in the order shown) if a cursor is to be incorporated into an application program: 1. Declare (define) a cursor, along with its type (read-only or updatable), and associate it with the desired query (SELECT or VALUES SQL statement). 2. Open the cursor. This action will cause the corresponding query to be executed and a result data set to be produced. 3. Retrieve (fetch) each row in the result data set, one by one, until an "End of data" condition occurs each time a row is retrieved from the result data set, the cursor is automatically moved to the next row. 4. If appropriate, modify or delete the current row (but only if the cursor is an updatable cursor). 5. Close the cursor. This action will cause the result data set that was produced when the corresponding query was executed to be deleted. With a DB2 UDB embedded SQL application, the following statements are used to carry out these steps: EXEC SQL DECLARE CURSOR EXEC SQL OPEN EXEC SQL FETCH EXEC SQL CLOSE The correct answer is C. Indoubt transactions are transactions that have completed the first phase, but not the second phase, of the two-phase commit process.
Question 18
SQL row function External scalar function External table function OLE DB external table function Function template CREATE FUNCTION Parameter passing styles Stored procedures SQL stored procedures External stored procedures CREATE PROCEDURE CALL Development Center
Techniques you will master: Understanding how and why user-defined data types are created Understanding how and why user-defined functions are created. Recognizing the differences among the user-defined data types available. Understanding how and why stored procedures are created. Understanding how the Development Center aids in the development of userdefined functions and stored procedures.
Table 8-1: DB2 UDB's Built-in Data Types Data Type Varying-Length Character String Definition(s) where Length is any number between 1 and 254 CHARACTER VARYING(MaxLength) <FOR BIT DATA>* CHAR VARYING(MaxLength) <FOR BIT DATA>* VARCHAR(MaxLength) <FOR BIT DATA>* where MaxLength is any number between 1 and 32,672 LONG VARCHAR*
Long VaryingLength Character String Fixed-Length Double-Byte Character String Varying-Length Double-Byte Character String Long VaryingLength DoubleByte Character String Date Time Timestamp Binary Large Object
GRAPHIC(Length) where Length is any number between 1 and 127 VARGRAPHIC(MaxLength) where MaxLength is any number between 1 and 16,336 LONG VARGRAPHIC
DATE TIME TIMESTAMP BINARY LARGE OBJECT(Size <K | M | G>) BLOB(Size <K | M | G>) where Size is any number between 1 and 2,147,483,647; if K (for kilobyte) is specified, Size is any number between 1 and 2,097,152; if M (for megabyte) is specified, Size is any number between 1 and 2,048; if G (for gigabyte) is specified, Size is any number between 1 and 2 CHARACTER LARGE OBJECT(Size <K | M | G>) CHAR LARGE OBJECT(Size<K | M | G>) CLOB(Size<K | M | G>) where Size is any number between 1 and 2,147,483,647; if K (for kilobyte) is specified, Size is any number between 1 and 2,097,152; if M (for megabyte) is specified, Size is any number between 1 and 2,048; if G (for gigabyte) is specified, Size is any number between 1 and 2 DBCLOB(Size<K | M | G>) where Size is any number between 1 and 1,073,741,823; if K (for kilobyte) is specified, Size is any number between 1 and 1,048,576; if M (for megabyte) is specified, Size is any number between 1 and 1,024; if G (for gigabyte) is specified, Size must be 1
*If the FOR BIT DATA option is used with any character string data type definition, the contents of the column that the data type is assigned to are treated as binary data. As a result, code page conversions are not performed if data is exchanged between other systems, and all comparisons made are done in binary, regardless of
Table 8-1: DB2 UDB's Built-in Data Types Data Type Definition(s)
the collating sequence used by the database. In addition to these "traditional" types of data, DB2 UDB provides facilities that can be used to create an infinite number of user-defined data types, which can in turn be used to store complex, nontraditional data that might be found in a complex computing environment.
Identifies the name that is to be assigned to the distinct user-defined data type that is to be created. SourceDataType Identifies the built-in data type that the distinct user-defined data type is to be derived from. (Table 8-1 contains a list of valid built-in data type definitions.) Note Although basic syntax is presented for most of the SQL statements covered in this chapter, the actual syntax supported may be much more complex. To view the complete syntax for a specific SQL statement or to obtain more information about a particular statement, refer to the IBM DB2 Universal Database, Version 8 SQL Reference Volume 2 product documentation.
So, if you wanted to create a distinct user-defined data type named MONEY that is based on the DECIMAL built-in data type, you could do so by executing a CREATE DISTINCT TYPE SQL statement that looks something like this: CREATE DISTINCT TYPE MONEY AS DECIMAL(11,2) WITH COMPARISONS
functionality will have to be re-invented by hand if you need the power the built-in data types provide.) Encapsulation. The set of functions and operators applied to a userdefined data type defines the behavior of that data type. This provides flexibility in implementation because applications do not have to be dependent on the internal representation used by a particular data type. Performance. Because distinct data types are internally represented in the same way as built-in data types, they share the same efficient code that is used to implement comparison operators and casting functions for the built-in data types available.
User-defined Functions
Earlier, we saw that when a distinct data type is created, six comparison functions and two casting functions are also created. These functions allow simple comparison operations to be performed and provide a way to seamlessly move data between a distinct data type and the builtin type on which it is based. However, other operations that might apply to a built-in data type are not automatically inherited. That's where user-defined functions (UDFs) come into play; userdefined functions are special objects that are used to extend and enhance the support provided by the built-in functions available with DB2 UDB. Like user-defined data types, user-defined functions (or methods) are created and named by a database user. However, unlike DB2 UDB's built-in functions, user-defined functions can take advantage of system calls and DB2 UDB's administrative APIs, thereby providing more synergy between applications and databases. Five types of user-defined functions can be created: Sourced (or Template). This type of function is implemented by invoking another function that is already registered in the database. (It is possible to create a partial function, called a function template, that defines what types of values are to be returned but contains no executable code. The function template is then mapped to a data source function within a federated system so that the data source function can be invoked from a federated database. A function template can be registered only with an application server that is designated as a federated server.) SQL Scalar, Table, or Row. This type of function, written entirely in SQL, can be scalar in nature (scalar functions return a single value and can be specified in an SQL statement wherever a regular expression can be used) or can return a single row or an entire table. External Scalar. This type of function, written using a high-level programming language, is scalar in nature. The function itself resides in an external library and is registered in the database, along with any related attributes. External Table. This type of function, written using a high-level programming language, returns a table to the SQL statement that references it and can only be specified in the FROM clause of a SELECT statement. Again, the function itself resides in an external library and is registered in the database, along with any related attributes. OLE DB External Table. This type of function, written using a high-level programming language, returns a table from an OLE DB provider to the SQL statement that references it. Like an external table function, an OLE DB external table function can only be specified in the FROM clause of a SELECT statement. The function resides in an external library and is registered in the database, along with any related attributes. User-defined functions are created (or registered) by executing the CREATE FUNCTION SQL statement. Several flavors of this statement are available, and the appropriate form to use is determined by the type of user-defined function to be created. (We'll take a look at each form as we examine each function type available.)
Sourced Functions
A sourced function is constructed from a function that has already been registered with a database (referred to as the source function). Sourced functions can be columnar, scalar, or table in nature or they can be designed to overload a specific operator such as +, , *, and /. When a sourced function is invoked, all arguments passed to it are converted to the data types that are expected by the underlying source function, and the source function itself is invoked. Upon completion, the source function performs any conversions necessary on the results
produced and returns control to the calling SQL statement. The most common use of sourced functions is to enable a user-defined distinct data type to selectively inherit some of the semantics of the built-in data type on which it is based. (Often, a user-defined function that is sourced on an existing function for the purpose of providing that function's support to a userdefined data type is given the same name as the sourced function used. This allows users to invoke the same function with a user-defined distinct type without realizing that an additional definition was required. Generally, the same name can be used for more than one function if there is some difference in each function's signature.) The basic syntax for the form of the CREATE FUNCTION statement used to create a sourced function looks something like this: CREATE FUNCTION [FunctionName] ( <[ParameterName]> [InputDataType] ,...> ) RETURNS [OutputDataType] <SPECIFIC [SpecificName]> SOURCE [SourceFunction] <([DataType] ,...)> <AS TEMPLATE> where: FunctionName ParameterName InputDataType OutputDataType SpecificName
Identifies the name to be assigned to the sourced function that is to be created. Identifies the name to be assigned to one or more function parameters. Identifies the type of data the function expects to receive for the parameter identified by ParameterName. Identifies the type of data the function is to return. Identifies the specific name to be assigned to the user-defined function. This name can be used later to refer to or delete (drop) the function; however, it cannot be used to invoke the function. Identifies the name that has been assigned to the existing function that is to be used to create the sourced function. Identifies the type of data that the existing function expects to receive for each parameter required. When a specific name is assigned to a user-defined function, the function can be dropped by referencing the specific name in a special form of the DROP SQL statement (DROP SPECIFIC FUNCTION [SpecificName]). However, if no specific name is assigned to a user-defined function, both the function name and the function's signature (a list of the data types used by each of the function's parameters, enclosed in parenthesis) must be provided as input to the DROP FUNCTION statement.
If the AS TEMPLATE clause is specified when this form of the CREATE FUNCTION statement is used, a function template will be produced. The function template must then be mapped to a data source function within a federated system before the data source function can be invoked from a federated database. So, if you created a distinct data type named YEAR that is based on the INTEGER built-in data type and wanted to create a sourced function named AVG() that accepts and returns a YEAR value and is based on the AVG() built-in function (which accepts and returns an INTEGER value), you could do so by executing a CREATE FUNCTION SQL statement that looks something like this: CREATE FUNCTION AVG(YEAR) RETURNS YEAR
SOURCE SYSIBM.AVG(INTEGER)
SQL Functions
Although a sourced function is constructed from a function that already exists, an SQL function is constructed from the ground, up, using only SQL statements. An SQL function can return a single scalar value, a table, or a row, depending on how it has been defined. The basic syntax for the form of the CREATE FUNCTION statement used to create an SQL function looks something like this: CREATE FUNCTION [FunctionName] ( <[ParameterName]> [InputDataType] ,...> ) RETURNS [[OutputDataType] | TABLE ( [ColumnName] [ColumnDataType],... ) | ROW ( [ColumnName] [ColumnDataType],... )] <SPECIFIC [SpecificName]> <LANGUAGE SQL> <DETERMINISTIC | NOT DETERMINISTIC> <READS SQL DATA | CONTAINS SQL> <CALLED ON NULL INPUT> [SQLStatements] | RETURN [ReturnStatement] where: FunctionName ParameterName InputDataType OutputDataType ColumnName ColumnDataType SpecificName
Identifies the name to be assigned to the SQL function that is to be created. Identifies the name to be assigned to one or more function parameters. Identifies the type of data the function expects to receive for the parameter identified by ParameterName. Identifies the type of data the function is to return. Identifies the name to be assigned to one or more columns that the function is to return (if the function is designed to return a table or a row). Identifies the type of data the function expects to return for the column identified by ColumnName. Identifies the specific name to be assigned to the user-defined function. This name can be used later to refer to or delete (drop) the function; however, it cannot be used to invoke the function. Identifies one or more SQL statements that are to be executed when the function is called. Together, these statements act as a single dynamic compound SQL statement. Identifies the RETURN SQL statement that is to be used to return to the application that called the function. (If the SQL function's body is comprised of a dynamic compound statement, it must contain at least one RETURN statement; otherwise, a RETURN statement must be executed when the function is called. If the function is a table or row function, it can contain only one RETURN statement, and that statement must be the last statement used.)
SQLStatements
ReturnStatement
If the DETERMINISTIC clause is specified when this form of the CREATE FUNCTION statement is used, it is implied that the function will always return the same scalar value, table, or row when it is called with the same parameter values specified. However, if the NOT DETERMINISTIC clause is specified (or if neither clause is specified) it is implied that the function may return different results each time it is called with the same parameter values specified.
Thus, if you wanted to create a scalar SQL function named TANGENT() that accepts and returns a DOUBLE value using the built-in functions SIN() (sine) and COS() (cosine), you could do so by executing a CREATE FUNCTION SQL statement that looks something like this: CREATE FUNCTION TANGENT (X DOUBLE) RETURNS DOUBLE LANGUAGE SQL CONTAINS SQL DETERMINISTIC RETURN SIN(X)/COS(X)
<DETERMINISTIC | NOT DETERMINISTIC> <FENCED | NOT FENCED> <RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT> <NO SQL | CONTAINS SQL | READS SQL DATA> where: FunctionName ParameterName InputDataType OutputDataType SpecificName
Identifies the name to be assigned to the external scalar function that is to be created. Identifies the name to be assigned to one or more function parameters. Identifies the type of data the function expects to receive for the parameter identified by ParameterName. Identifies the type of data the function is to return. Identifies the specific name to be assigned to the user-defined function. This name can be used later to refer to or delete (drop) the function; however, it cannot be used to invoke the function. Identifies the name of the library and the name of the function in the library that contains the executable code of the external function being registered. (We'll take a closer look at how this name is constructed shortly.) Identifies the name of the library that contains the executable code of the external function being registered, but only if the procedure was written using C or C++. The DB2 Database Manager will look for a function that has the same name as the library name specified.
ExternalName
Identifier
As you can see, this form of the CREATE FUNCTION statement contains several additional clauses that are not found in the other forms of the CREATE FUNCTION statement that we have already looked at. In many cases, it is not immediately apparent what information these clauses are trying to convey, so before we look at an example, let's examine some of these clauses in detail. EXTERNAL <NAME [ExternalName] | [Identifier]>. This clause is used to identify two things: the name of the library and, optionally, the name of the function within the library that contains the executable code for the userdefined function being registered. The high-level programming language used to construct the body of any external user-defined function determines how these names are provided. For example, if an external user-defined function was developed using the C or C++ programming language, the names of the library and function within the library that contains the body of the function can be specified in four different ways: LibraryName LibraryName ! FunctionName AbsolutePath AbsolutePath ! FunctionName If a library name is provided instead of an absolute path, the DB2 Database Manager will look in the /sqllib/function and /sqllib/function/unfenced subdirectories (\sqllib\function and \sqllib\function\unfenced subdirectories on Windows) for the library name specified. On the other hand, if an absolute path is provided, the name of the library must be appended to the path, and the DB2 Database Manager will look in the location specified for the appropriate library. (If neither a library name nor an absolute path is provided, the DB2 Database Manager will look in the default subdirectories shown earlier for a library and function that has the same name as the name that is to be assigned to the user-defined function being registered.) If a function name is provided, the DB2 Database Manager will look for a function that has the name specified within the library specified; if no function name is provided, the DB2 Database Manager will look for a function that has the same name as the library name specified. PARAMETER STYLE [DB2GENERAL | JAVA | SQL]. This clause is used to identify the parameter passing style that the user-defined function expects the calling application to use when passing values to it. As you can see, three parameter passing styles are available:
DB2GENERAL. Values are passed and returned using the calling conventions that are used to call a method in a Java class. (Can only be used when the external user-defined function is written using Java.) JAVA. Values are passed and returned using calling conventions that conform to the Java language and SQLJ specifications. (Can only be used when the external user-defined function is written using Java.) SQL. Values are passed and returned using calling conventions that are defined in the SQL/Persistent Stored Modules ISO working draft; along with the arguments identified, the following are passed to the stored procedure when it is called: a null indicator for each parameter passed, a placeholder for the SQLSTATE to be returned in, the qualified name of the stored procedure, the specific name of the stored procedure, and a placeholder for the SQL diagnostic string to be returned in. (Can only be used when the external user-defined function is written using C/C++ or OLE.) <FENCED | NOT FENCED>. This clause is used to identify whether the external user-defined function is considered "safe" enough to be run in the DB2 Database Manager operating environment's process/address space (NOT FENCED), or not (FENCED). If the FENCED clause is specified (or if neither clause is specified) the DB2 Database Manager will not allow the function to access its internal resources. <NO SQL | CONTAINS SQL | READS SQL DATA>. This clause is used to identify which types of SQL statements have been coded in the body of the external user-defined function. Three different values are available: NO SQL. The body of the external user-defined function either does not contain any SQL or it contains non-executable SQL statements. (Examples of non-executable SQL statements are the INCLUDE and WHENEVER statements.) CONTAINS SQL. The body of the external user-defined function contains executable SQL statements that neither read nor modify data. READS SQL DATA. The body of the external user-defined function contains executable SQL statements that read but do not modify data. Thus, if you wanted to register an external scalar function named CENTER() that accepts two values (an INTEGER value and a DOUBLE value), returns a DOUBLE value, and is stored in a library named "double" that resides in the directory "/home/db2inst1/myfuncs", you could do so by executing a CREATE FUNCTION SQL statement that looks something like this: CREATE FUNCTION CENTER(INT, DOUBLE) RETURNS DOUBLE EXTERNAL NAME '/home/db2inst1/myfuncs/double' LANGUAGE C PARAMETER STYLE SQL DETERMINISTIC NO SQL
( <[ParameterName]> [InputDataType] ,...> ) RETURNS TABLE ( [ColumnName] [ColumnDataType],... ) <SPECIFIC [SpecificName]> EXTERNAL <NAME [ExternalName] | [Identifier]> LANGUAGE [C | JAVA | OLE] PARAMETER STYLE [DB2GENERAL | SQL] <DETERMINISTIC | NOT DETERMINISTIC> <FENCED | NOT FENCED> <RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT> <READS SQL DATA | NO SQL | CONTAINS SQL> where: FunctionName ParameterName InputDataType ColumnName ColumnDataType SpecificName
Identifies the name to be assigned to the external table function that is to be created. Identifies the name to be assigned to one or more function parameters. Identifies the type of data the function expects to receive for the parameter identified by ParameterName. Identifies the name to be assigned to one or more columns that the function is to return (if the function is designed to return a table or a row). Identifies the type of data the function expects to return for the column identified by ColumnName. Identifies the specific name to be assigned to the user-defined function. This name can be used later to refer to or delete (drop) the function; however, it cannot be used to invoke the function. Identifies the name of the library and the name of the function in the library that contains the executable code of the external function being registered. Identifies the name of the library that contains the executable code of the external function being registered, but only if the procedure was written using C or C++. The DB2 Database Manager will look for a function that has the same name as the library name specified.
ExternalName
Identifier
So, if you wanted to register an external table function named EMPDATA() that accepts two variable-length character string values, returns a result data set that contains employee information (retrieved from an ASCII file), and is stored in a library named "EMPDATA" that resides in the directory "/home/db2inst1/myfuncs", you could do so by executing a CREATE FUNCTION SQL statement that looks something like this: CREATE FUNCTION EMPDATA (VARCHAR(30), VARCHAR(255)) RETURNS TABLE (EMPID INT, LNAME CHAR(20), FNAME CHAR(20)) EXTERNAL NAME '/home/db2inst1/myfuncs/EMPDATA' LANGUAGE C PARAMETER STYLE SQL NO SQL DETERMINISTIC NO EXTERNAL ACTION NOT FENCED
Identifies the name to be assigned to the OLE DB external table function that is to be created. Identifies the name to be assigned to one or more function parameters. Identifies the type of data the function expects to receive for the parameter identified by ParameterName. Identifies the name to be assigned to one or more columns the function is to return (if the function is designed to return a table or a row).
ColumnDataType SpecificName
Identifies the type of data the function expects to return for the column identified by ColumnName. Identifies the specific name to be assigned to the user-defined function. This name can be used later to refer to or delete (drop) the function; however, it cannot be used to invoke the function. Identifies the name of the external table being referenced and the name of the OLE DB provider.
ExternalName
The syntax used to specify the external table name and the OLE DB provider is: '[Server]!<Rowset>' or '!<Rowset>![ConnectString] <!COLLATING_SEQUENCE = [N | Y]>' where: Server Rowset ConnectString
Identifies the local name of a data source as defined by the CREATE SERVER SQL statement. Identifies the rowset (table) that is exposed by the OLE DB provider. Specifies a connection string that identifies the initialization properties that are needed to connect to a data source via an OLE DB provider. This string consists of a series of keyword=value pairs that are similar to those used to construct a connection string for the CLI/ODBC function SQLDriverConnect().
The <!COLLATING_SEQUENCE = [N | Y]> option is used to specify whether the data source being accessed uses the same collating sequence as DB2 UDB. Thus, if you wanted to register an OLE DB external table function named ORDERS() that retrieves order information from a Microsoft Access database, you could do so by executing a CREATE FUNCTION SQL statement that looks something like this: CREATE FUNCTION ORDERS() RETURNS TABLE (ORDERID INTEGER, CUSTOMERID CHAR(5), EMPLOYEEID INTEGER, ORDERDATE TIMESTAMP, REQUIREDDATE TIMESTAMP, SHIPPEDDATE TIMESTAMP, SHIPCHARGES DEC(19,4)) LANGUAGE OLEDB EXTERNAL NAME '!orders!Provider=Microsoft.Jet.OLEDB.3.51; Data Source=c:\sqllib\samples\oledb\nwind.mdb'
operation without having to duplicate the code used to perform the operation. Additionally, because the DB2 Database Manager will take care of many data type conversions, once a function is created, the potential for it to be used in more ways than was originally conceived is high. Increased availability. If the code used to perform a particular operation is embedded in an application, only the users of that application are allowed to perform the operation; interactive users, such as those who use the Command Line Processor (CLP) to access a database, cannot take advantage of the functionality that such an application provides. This is not the case when the code needed to perform the operation is incorporated into a user-defined function; both interactive users and application users can perform the operation or action once it has been embodied in a user-defined function. Increased performance. An operation that has been embodied in a user-defined function will often execute faster than a similar operation that has been incorporated into an application, particularly when the operation is used to qualify data for further processing. For example, suppose you have a need to extract some information from a large object (LOB) data value. If you have a function that performs this operation, the extraction can take place directly at the database server, and only the extracted value will need to be passed to a user or a calling application. This is much more efficient than sending the entire LOB value to an application or user and requiring the extraction to take place on a client workstation. Encapsulation. When you create a distinct data type, you are automatically provided with functions for casting between the distinct type and its source type, and in most cases, you are provided with comparison functions, as well (depending on the source data type used). However, you must provide any additional behavior yourself. Because it is clearly desirable to keep the behavior of a distinct data type in the database where all users of the distinct type can easily access its behaviors, user-defined functions can be used to encapsulate distinct data types with operations that are designed to only be used with them.
Stored Procedures
Earlier, we saw that when you set up a remote DB2 UDB database server and access it from one or more DB2 UDB client workstations, you have, in essence, established a basic DB2 UDB client/server environment. In such an environment, each time an SQL statement is executed against the database on the remote server, the statement itself is sent through a network from the client workstation to the database server. The database server then processes the statement, and the results are sent back, again through the network, to the client workstation. (This means that two messages must go through the network for every SQL statement that is executed.) To take advantage of this architecture, client/server application development focuses on breaking an application into two separate parts, storing those parts on two different platforms (the client and the server), and having them communicate with each other as the application executes. This allows the code that interacts directly with a database to reside on a database server or midrange computer where computing power and centralized control can be used to provide quick, coordinated data access. At the same time, the application logic can reside on one or more smaller (client) workstations so it can make effective use of all the resources the client workstation has to offer without causing a bottleneck at the server. Figure 8-1 illustrates how such an application works in a DB2 UDB client/server environment. If you have an application that contains one or more transactions that perform a relatively large amount of database activity with little or no user interaction, each transaction can be stored on the database server as a stored procedure. With a stored procedure, all database processing done by the transaction can be performed directly at the database server. And because a stored procedure is invoked by a single SQL statement, fewer messages have to be transmitted across the networkonly the data that is actually needed at the client workstation has to be sent across. Figure 8-2 illustrates how a DB2 UDB application using a stored procedure works in a client/server environment. Client/server applications that use stored procedures have the following advantages over client/server applications that do not:
Figure 8-1: How client/server applications work in a DB2 UDB client/server environment. Reduced network traffic. Messages are not sent across the network for SQL statements that are coded in a stored procedure. If a stored procedure is designed correctly, only data that is needed by the client application will be sent across the network. Improved performance of server-intensive work. Because less data has to be sent across the network, and because processing is done right at the server, complex queries and other serverintensive work can execute faster. Ability to separate and reuse business logic. When business rules are incorporated into stored procedures, the logic can be reused multiple times by simply calling the stored procedure as needed. Because the logic used in a stored procedure can be modified independently, applications do not have to be recoded when business rules change. Ability to access features that exist only on the server. Because stored procedures run directly on the server workstation, they can take advantage of any extra memory, faster processor(s), etc. that the database server might have. (Typically, database servers have more memory and multiple or faster processors than client workstations.) Additionally, stored procedures can take advantage of DB2's set of administrative APIs, which can be run only at the server. Finally, because stored procedures are not restricted to performing databaseonly related activities, they can take advantage of any additional software that has been installed on the server workstation.
Figure 8-2: How DB2 UDB applications using stored procedures work in a client/server environment. Applications that use stored procedures do so under the restriction that all input data must be passed from application to the stored procedure at invocation time and that any result data sets produced by the stored procedure will not be returned to the application until the stored procedure completes execution. In other words, no interactions can occur between the application and the stored procedure while the stored procedure is running.
of stored procedure that is to be created. (We'll take a look at each form as we examine each type of procedure available.)
Identifies the name to be assigned to the procedure to be created. Indicates whether the parameter identified by ParamName is an input parameter (IN), an output parameter (OUT), or both an input parameter and an output parameter (INOUT). (Valid values include IN, OUT, and INOUT.) Identifies the name to be assigned to a procedure parameter. Identifies the type of data the procedure expects to receive or send for the parameter identified by ParamName. Identifies the specific name to be assigned to the stored procedure. This name can be used later to comment on the stored procedure or drop the stored procedure; however, it cannot be used to invoke the stored procedure. Identifies whether the stored procedure being registered returns result data sets and, if so, how many. Identifies a single SQL statement or a compound SQL statement (i.e., two or more SQL statements enclosed with the keywords BEGIN ATOMIC and END and terminated with a semicolon) that is to be executed when the stored procedure is invoked. When a specific name is assigned to a stored procedure, the procedure can be dropped by referencing the specific name in a special form of the DROP SQL statement (DROP SPECIFIC PROCEDURE [SpecificName]). However, if no specific name is assigned to a stored procedure, both the procedure name and the procedure's signature (a list of the data types used by each of the stored procedure's parameters) must be provided as input to the DROP PROCEDURE statement.
NumResultSets SQLStatement
Note
As you can see, some of the clauses that are used with this form of the CREATE PROCEDURE SQL statement are identical to those used by the CREATE FUNCTION SQL statement. A simple SQL stored procedure could be created by executing a CREATE PROCEDURE statement that looks something like this: CREATE PROCEDURE GET_SALES
(IN QUOTA INTEGER, OUT RETCODE CHAR(5)) DYNAMIC RESULT SETS 1 LANGUAGE SQL BEGIN DECLARE SQLSTATE CHAR(5); DECLARE SALES_RESULTS CURSOR WITH RETURN FOR SELECT SALES_PERSON, SUM(SALES) AS TOTAL_SALES FROM SALES GROUP BY SALES_PERSON HAVING SUM(SALES) > QUOTA; DECLARE EXIT HANDLER FOR SQLEXCEPTION SET RETCODE = SQLSTATE; OPEN SALES_RESULTS; SET RETCODE = SQLSTATE; END The resulting SQL stored procedure, called GET_SALES, accepts an integer input value (in an input parameter called QUOTA) and returns a character value (in an output parameter called RETCODE) that reports the procedure's success or failure. The procedure body consists of a compound SQL statement that returns a result data set (i.e., an open cursor) containing the name and total sales figures for each salesperson whose total sales exceed the quota specified in a result data set. This is done by: 1. Indicating that the SQL procedure is to return a result data set by specifying the DYNAMIC RESULT SETS clause of the CREATE PROCEDURE statement and assigning it the value 1. 2. Declaring a cursor within the procedure body (using the WITH RETURN FOR clause) for the result data set that is to be returned. (Earlier, we saw that a cursor is a named control structure that points to a specific row within a set of rows and is used by an application program to retrieve values from this set of rows.) 3. Opening the cursor, which produces the result data set that is to be returned. 4. Leaving the cursor open when the SQL procedure ends. It is important to note that when an SQL stored procedure is used to implement a business rule, the logic used to apply that business rule can be incorporated into any application simply by invoking the stored procedure. Thus, the same business rule logic is guaranteed to be enforced across all applications. When business rules change, only the logic in the SQL stored procedure needs to be changed; applications that call the procedure do not have to be modified. (The same can be said for external stored procedures, but the steps required to change the business logic coded in an external stored procedure is a little more complex.)
Construct the body of the stored procedure, using a supported high-level programming language. Compile the stored procedure. Link the stored procedure to create a library (or dynamic-link library). Debug the stored procedure and repeat steps 2 through 4 until all problems have been resolved. Physically store the library containing the stored procedure on the database server. By default, the DB2 Database Manager looks for external stored procedures in the /sqllib/function and /sqllib/function/unfenced subdirectories (\sqllib\function and \sqllib\function\ unfenced subdirectories on Windows). Additionally, the system permissions for the library file containing the stored procedure must be modified so that all users can execute it. For example, in a UNIX environment, the chmod command is used to make a file executable; in a Windows environment, the attrib command is used for the same purpose. Register the stored procedure with a DB2 database using the appropriate form of the CREATE PROCEDURE SQL statement.
The basic syntax for the form of the CREATE PROCEDURE statement used to register an external stored procedure (that has been created as outlined above) looks something like this: CREATE PROCEDURE [ProcedureName] ( [ParamType] [ParamName] [DataType] ,...) <SPECIFIC [SpecificName]> <DYNAMIC RESULT SETS [NumResultSets]> <NO SQL | CONTAINS SQL | READS SQL DATA> <DETERMINISTIC | NOT DETERMINISTIC> <CALLED ON NULL INPUT> LANGUAGE [C | JAVA | COBOL | OLE] EXTERNAL <NAME [ExternalName] | [Identifier]> <FENCED <THREADSAFE | NOT THREADSAFE> | NOT FENCED <THREADSAFE>> PARAMETER STYLE [DB2GENERAL | DB2SQL | GENERAL | GENERAL WITH NULLS | JAVA | SQL] <PROGRAM TYPE [SUB | MAIN]> <DBINFO | NO DBINFO> where: ProcedureName ParamType
Identifies the name to be assigned to the procedure to be created. Indicates whether the parameter identified by ParamName is an input parameter (IN), an output parameter (OUT), or both an input parameter and an output parameter (INOUT). (Valid values include IN, OUT, and INOUT.) Identifies the name to be assigned to a procedure parameter. Identifies the type of data the procedure expects to receive or send for the parameter identified by ParamName. Identifies the specific name to be assigned to the stored procedure. This name can be used later to comment on or drop the stored procedure; however, it cannot be used to invoke the stored procedure.
NumResultSets SQLStatements
Identifies whether the stored procedure being registered returns result data sets and, if so, how many. Specifies one or more SQL statements that are to be executed when the stored procedure is invoked. These statements make up the body of an SQL procedure. Identifies the name of the library, along with the name of the function in the library, that contains the executable code of the stored procedure being registered. Identifies the name of the library that contains the executable code of the stored procedure being registered, but only if the procedure was written using C or C++. The DB2 Database Manager will look for a function that has the same name as the library name specified.
ExternalName
Identifier
Again, many of the clauses used with this form of the CREATE PROCEDURE SQL statement are similar to those used by the CREATE FUNCTION SQL statement. For example: <DETERMINISTIC | NOT DETERMINISTIC>. This clause is used to identify whether the stored procedure will always return the same results when passed the same parameter values (DETERMINISTIC) or not (NOT DETERMINISTIC). (A stored procedure that applies a 15% increase to any value passed to it would be considered DETERMINISTIC, while a stored procedure that generates a unique ID using the TIMESTAMP_ISO() function would be considered NOT DETERMINISTIC.) <CALLED ON NULL INPUT>. When this clause is used, the stored procedure will always be invoked, even if a null value is passed for one or more of its input parameters. <NO SQL | CONTAINS SQL | READS SQL DATA | MODIFIES SQL DATA>. This clause is used to identify which types of SQL statements have been coded in the body of the external stored procedure. Four different values are available: NO SQL. The body of the stored procedure either does not contain any SQL or it contains nonexecutable SQL statements. (Examples of non-executable SQL statements are the INCLUDE and WHENEVER statements.) CONTAINS SQL. The body of the stored procedure contains executable SQL statements that neither read nor modify data. READS SQL DATA. The body of the stored procedure contains executable SQL statements that read but do not modify data. MODIFIES SQL DATA. The body of the stored procedure contains executable SQL statements that both read and modify data. PARAMETER STYLE [DB2GENERAL | DB2SQL | GENERAL | GENERAL WITH NULLS | JAVA | SQL]>. This clause is used to identify the parameter passing style that the stored procedure expects the calling application to use when passing values to it. As you can see, there are six parameter passing styles available: DB2GENERAL. Values are passed and returned using the calling conventions that are used to call a method in a Java class. (Can only be used when the stored procedure is written using Java.) DB2SQL. Values are passed and returned using calling conventions that are defined in the SQL/Persistent Stored Modules ISO working draft; along with the arguments identified, the following are passed to the stored procedure when it is called: a null indicator for each parameter passed, a placeholder for the SQLSTATE to be returned in, the qualified name of the stored procedure, the specific name of the stored procedure, and a placeholder for the SQL diagnostic string to be returned in. (Can only be used when the stored procedure is written using C/C++, COBOL, or OLE.) GENERAL. Values are passed and returned exactly as they are specified when the stored procedure is invoked. (Can only be used when the stored procedure is written using C/C++ or COBOL.) GENERAL WITH NULLS. Same as GENERAL, with one major differencean additional argument containing a vector of null indicators is also passed to and returned from the stored procedure. (Can only be used when the stored procedure is written using C/C++ or COBOL.)
JAVA. Values are passed and returned using calling conventions that conform to the Java language and SQLJ specifications. (Can only be used when the stored procedure is written using Java.) SQL. Same as DB2SQL. <PROGRAM TYPE [SUB | MAIN]>. This clause is used to identify whether the stored procedure was defined as a main routine (MAIN) or as a subroutine (SUB). If the PROGRAM TYPE SUB clause is specified, the DB2 Database Manager will pass all parameter values to the stored procedure as separate arguments. In this case, the stored procedure can be assigned any name that conforms to the function naming conventions allowed by the high-level programming language used. On the other hand, if the PROGRAM TYPE MAIN clause is specified, the DB2 Database Manager will pass all parameter values to the stored procedure as a combination of an argument counter and an array of argument values. In this case, the DB2 Database Manager expects the name of the stored procedure to be "main." <DBINFO | NO DBINFO>. This clause is used to identify whether information known by DB2 is to be passed to the stored procedure as an additional argument when it is invoked (DBINFO) or not (NO DBINFO). If the DB2INFO clause is used, the DB2 Database Manager will pass a data structure that contains the following information to the stored procedure at the time it is invoked: The name of the currently connected database. The unique application ID that is established for each connection to the database. The application run-time authorization ID. The database code page. The version, release, and modification level of the database server invoking the stored procedure. The operating system being used by the server. Thus, if you wanted to register an external stored procedure named EXTRACT_RESUME that is stored as a function named "ExResume" in a library named "SProc" that resides in the directory "C:\StoredProcs", you could do so by executing a CREATE PROCEDURE SQL statement that looks something like this: EXEC SQL CREATE PROCEDURE EXTRACT_RESUME (IN FILENAME VARCHAR(255), IN EMPNO CHAR(6)) SPECIFIC EXTRACT_RESUME DYNAMIC RESULT SETS 0 EXTERNAL NAME 'C:\StoredProcs\SProc!ExResume' LANGUAGE C PARAMETER STYLE GENERAL DETERMINISTIC FENCED CALLED ON NULL INPUT PROGRAM TYPE SUB; When this particular CREATE PROCEDURE SQL statement is executed, an external stored procedure will be registered that: Has been assigned the name EXTRACT_RESUME. Has one input parameter called FILENAME that expects a VARCHAR(255) data value and another input parameter called EMPNO that expects a CHAR(6) data value. Has been assigned the specific name EXTRACT_RESUME. Does not return a result data set. Was constructed using the C or C++ programming language.
Expects calling applications to use the GENERAL style when passing parameters. Will always return the same results if called with the same parameter values. Is to be run outside the DB2 Database Manager operating environment's address space. Is to be called, even if one of its parameters contains a null value. Was written as a subroutine
Identifies the name assigned to the procedure to be invoked. (Remember, the procedure name, not the specific name, must be used to invoke the procedure.) Identifies one or more parameter values that are to be passed to the procedure being invoked. Identifies one or more parameter markers or host variables that are to receive return values from the procedure being invoked.
InputParameter OutputParameter
Thus, the SQL procedure named GET_SALES that we created earlier could be invoked by connecting to the appropriate database and executing a CALL statement from the Command Line Processor that looks something like this: CALL GET_SALES (25, ?) The same procedure could be invoked from an embedded SQL application by executing a CALL statement that looks something like this: CALL GET_SALES (25, :RetCode) where RetCode is the name of a valid host variable. When this CALL statement is executed, the value 25 is passed to the input parameter named QUOTA, and a question mark (?) or a host variable named RetCode is used as a place-holder for the value that will be returned in the output parameter named RETCODE.
[Java routines] or server [SQL routines], copies the source code and resulting library to the server, and registers the routine in the system catalog of the database being used.) With the Development Center, users can: Create, build, and deploy user-defined functions, including SQL scalar functions, SQL table functions, OLE DB table functions, functions that read MQSeries messages, and functions that extract data from XML documents. (You can manually create user-defined functions using the builtin editor or automate the process by using one of the Wizards available.) Create, build, and deploy SQL and Java stored procedures. (You can manually create SQL and Java stored procedures using the built-in editor or automate the process by using one of the Wizards available.) Debug SQL stored procedures using the integrated debugger. Create and build structured data types. View and work with database objects such as tables, triggers, and views. Export and import routines and project information. The Development Center can be launched as a separate application from the DB2 Universal Database program group or it can be started as a separate process from the Control Center toolbar, Tools menu, or Stored Procedures folder. Figure 8-3 shows what the Development Center looks like on a Windows 2000 server.
Figure 8-3: The Development Center. The Development Center also provides a DB2 Development Add-In for each of the following interactive development environments: Microsoft Visual Studio Microsoft Visual Basic Microsoft Visual InterDev IBM VisualAge for Java Using these add-ins, application developers can quickly and easily access the features of the Development Center directly from a Microsoft or IBM VisualAge development tool.
Practice Questions
Question 1 Given the following type definition: CREATE DISTINCT TYPE speed_limit AS DECIMAL (5,0) WITH COMPARISONS Which of the following is the best choice for the implementation of the addition operation for two values of the SPEED_LIMIT data type? A. An SQL scalar UDF B. An external table UDF C. A sourced UDF D. An SQL row UDF
Question 2
Question 3
Question 4
Question 5
Which of the following is NOT a specific benefit of using stored procedures in a client/server environment? A. Ability to reduce data flow over the network B. Ability to return user-defined messages C. Ability to separate and reuse business logic D. Ability to improve performance of server-intensive work The Development Center can be used to develop which of the following? A. External scalar UDFs B. External table UDFs C. SQL and Java stored procedures D. C/C++ and Java stored procedures Which of the following is NOT a valid parameter passing style for a UDF? A. GENERAL B. JAVA C. SQL D. DB2GENERAL Given the statement: CREATE PROCEDURE proc1 (IN p1 INTEGER) RESULT SETS 1 LANGUAGE SQL P1:BEGIN DECLARE c1 CURSOR WITH RETURN FOR SELECT * FROM tab1; OPEN c1; P1:END Which of the following accurately describes the database object that will be created? A. An SQL stored procedure that accepts an integer value as input and returns a cursor for all rows found in table TAB1 to the procedure caller B. An external stored procedure that accepts an integer value as input and returns a cursor for all rows found in table TAB1 to the procedure caller C. An SQL stored procedure that accepts an integer value as input and returns an array containing all rows found in table TAB1 to the procedure caller D. An SQL stored procedure that accepts an integer value as input and returns nothing to the procedure caller because the TO CALLER clause does not follow the WITH RETURN clause of the DECLARE CURSOR statement.
Question 6
Question 7
Question 8
Which of the following does NOT accurately describe the Development Center? A. The Development Center is in interactive GUI application B. The Development Center can be used to develop applications for the entire DB2 Family, including DB2 UDB for iSeries (AS/400) and DB2 for zSeries (z/OS, OS/390) C. The Development Center precompiles and compiles the source code for an SQL stored procedure on the server, copies the source code and resulting library to the server, and registers the routine in the system catalog of the database being used D. Because the Development Center cannot be used to develop C/C+ + stored procedures, it does not interact with Microsoft Visual Studio. Four different applications need to make a business measurement calculation that involves performing several different SQL operations. These applications will be run from a number of different client workstations that communicate to a single server workstation over a 10/100 Ethernet network. Which of the following should be used to perform this calculation while providing maximum application performance? A. An SQL stored procedure B. An SQL scalar UDF C. An external scalar UDF D. An external table UDF Given the following SQL stored procedure: CREATE PROCEDURE myStoredProc(IN p1 INTEGER, IN p2 CHAR(5)) LANGUAGE SQL SPECIFIC SProc BEGIN IF (p1 = 0 OR p1 = 1) THEN UPDATE tab1 SET col1 = p2; END IF; END Which of the following will remove this stored procedure from the database? A. DROP PROCEDURE myStoredProc() B. DROP SPECIFIC PROCEDURE SProc C. DROP PROCEDURE myStoredProc(p1, p2) D. DROP SPECIFIC PROCEDURE myStoredProc() The correct answer is C. A sourced UDF is a user-defined
Answers Question
Question 2
Question 3
Question 4
Question 5
Question
function that is constructed from a function that has already been registered with a database (referred to as the source function). Sourced functions can be columnar, scalar, or table in nature or they can be designed to overload a specific operator such as +, , *, and /. (SQL scalar UDFs, external table UDFs, and SQL row UDFs cannot be used to overload the addition operator.) The correct answer is B. Client/server applications that use stored procedures have the following advantages over client/server applications that do not: Reduced network traffic Improved performance of server-intensive work Ability to separate and reuse business logic Ability to access features that only exists on the server The correct answer is C. With the Development Center, you can create, build, and deploy SQL scalar UDFs, SQL table UDFs, OLE DB UDFs, UDFs that read MQSeries messages, UDFs that extract data from XML documents, SQL stored procedures, and Java stored procedures. (The Development Center cannot be used to create external UDFs or stored procedures that are to be written in C/C++ or COBOL.) The correct answer is A. Three parameter passing styles can be used when registering external user-defined functions available. They are: DB2GENERAL. Values are passed and returned using the calling conventions that are used to call a method in a Java class. (Can be used only when the external user-defined function is written using Java.) JAVA. Values are passed and returned using calling conventions that conform to the Java language and SQLJ specifications. (Can be used only when the external userdefined function is written using Java.) SQL. Values are passed and returned using calling conventions that are defined in the SQL/Persistent Stored Modules ISO working draft; along with the arguments identified, the following are passed to the stored procedure when it is called: a null indicator for each parameter passed, a placeholder for the SQLSTATE to be returned in, the qualified name of the stored procedure, the specific name of the stored procedure, and a placeholder for the SQL diagnostic string to be returned in. (Can be used only when the external user-defined function is written using C/C++ or OLE.) The correct answer is A. Because the body of the stored procedure contains nothing but SQL statements and because the LANGUAGE SQL clause was used with the CREATE PROCEDURE statement, the resulting stored procedure must be an SQL stored procedure. The statement (IN p1 INTEGER) that follows the procedure name indicates that the procedure accepts an integer value as input, and the DECLARE CURSOR statement indicates that all rows found in the table TAB1 are to be copied to a cursor. Finally, the RESULT SETS 1 clause of the CREATE PROCEDURE statement, coupled with the WITH HOLD clause of the DECLARE CURSOR statement, indicates that the cursor containing all rows found in the table TAB1 will be returned to the procedure caller when the procedure ends. The correct answer is D. The Development Center provides a
DB2 Development Add-In for each of the following interactive development environments: Microsoft Visual Studio Microsoft Visual Basic Microsoft Visual InterDev IBM VisualAge for Java Using these add-ins, application developers can quickly and easily access the features of the Development Center directly from a Microsoft or IBM VisualAge development tool. The correct answer is A. In this case, an SQL stored procedure will eliminate duplication of code and will reduce network traffic over a relatively slow network. The correct answer is B. When a specific name is assigned to a stored procedure, the procedure can be dropped by referencing the specific name in a special form of the DROP SQL statement (DROP SPECIFIC PROCEDURE [SpecificName]). However, if no specific name is assigned to a stored procedure, both the procedure name and the procedure's signature (a list of the data types used by each of the stored procedure's parameters) must be provided as input to the DROP PROCEDURE statement.
Question 7 Question 8