Using descriptors v15
Dynamic SQL allows a client application to execute SQL statements that are composed at runtime. This ability is useful when you don't know the content or form for a statement when you're writing a client application. ECPGPlus doesn't allow you to use a host variable in place of an identifier (such as a table name, column name, or index name). Instead, use dynamic SQL statements to build a string that includes the information, and then execute that string. The string is passed between the client and the server in the form of a descriptor. A descriptor is a data structure that contains both the data and the information about the shape of the data.
Overview of the client application flow
A client application must use a GET DESCRIPTOR
statement to retrieve information from a descriptor. The basic flow of a client application using dynamic SQL is:
- Use an
ALLOCATE DESCRIPTOR
statement to allocate a descriptor for the result set (select list). - Use an
ALLOCATE DESCRIPTOR
statement to allocate a descriptor for the input parameters (bind variables). - Obtain, assemble, or compute the text of an SQL statement.
- Use a
PREPARE
statement to parse and check the syntax of the SQL statement. - Use a
DESCRIBE
statement to describe the select list into the select-list descriptor. - Use a
DESCRIBE
statement to describe the input parameters into the bind-variables descriptor. - Prompt the user (if required) for a value for each input parameter. Use a
SET DESCRIPTOR
statement to assign the values into a descriptor. - Use a
DECLARE CURSOR
statement to define a cursor for the statement. - Use an
OPEN CURSOR
statement to open a cursor for the statement. - Use a
FETCH
statement to fetch each row from the cursor, storing each row in select-list descriptor. - Use a
GET DESCRIPTOR
command to interrogate the select-list descriptor to find the value of each column in the current row. - Use a
CLOSE CURSOR
statement to close the cursor and free any cursor resources.
Descriptor attributes
A descriptor can contain these attributes.
Field | Type | Attribute description |
---|---|---|
CARDINALITY | integer | The number of rows in the result set. |
DATA | N/A | The data value. |
DATETIME_INTERVAL_CODE | integer | If TYPE is 9 : 1 - DATE 2 - TIME 3 - TIMESTAMP 4 - TIME WITH TIMEZONE 5 - TIMESTAMP WITH TIMEZONE |
DATETIME_INTERVAL_PRECISION | integer | Unused. |
INDICATOR | integer | Indicates a NULL or truncated value. |
KEY_MEMBER | integer | Unused (returns FALSE ). |
LENGTH | integer | The data length (as stored on server). |
NAME | string | The name of the column in which the data resides. |
NULLABLE | integer | Unused (returns TRUE ). |
OCTET_LENGTH | integer | The data length (in bytes) as stored on server. |
PRECISION | integer | The data precision (if the data is of numeric type). |
RETURNED_LENGTH | integer | Actual length of data item. |
RETURNED_OCTET_LENGTH | integer | Actual length of data item. |
SCALE | integer | The data scale (if the data is of numeric type). |
TYPE | integer | A numeric code that represents the data type of the column: 1 - SQL3_CHARACTER 2 - SQL3_NUMERIC 3 - SQL3_DECIMAL 4 - SQL3_INTEGER 5 - SQL3_SMALLINT 6 - SQL3_FLOAT 7 - SQL3_REAL 8 - SQL3_DOUBLE_PRECISION 9 - SQL3_DATE_TIME_TIMESTAMP 10 - SQL3_INTERVAL 12 - SQL3_CHARACTER_VARYING 13 - SQL3_ENUMERATED 14 - SQL3_BIT 15 - SQL3_BIT_VARYING 16 - SQL3_BOOLEAN |
Example: Using a descriptor to return data
The following simple application executes an SQL statement entered by an end user. The code sample shows:
- How to use a SQL descriptor to execute a
SELECT
statement. - How to find the data and metadata returned by the statement.
The application accepts an SQL statement from an end user, tests the statement to see if it includes the SELECT
keyword, and executes the statement.
Using a SQL descriptor to execute a SELECT
statement
When invoking the application, an end user must provide the name of the database on which to perform the SQL statement and a string that contains the text of the query.
For example, a user might invoke the sample with the following command:
The code sample begins by including the prototypes and type definitions for the C stdio
and stdlib
libraries, SQL data type symbols, and the SQLCA
(SQL communications area) structure:
The sample provides minimal error handling. When the application encounters a SQL error, it prints the error message to screen:
Finding the data and metadata returned by the statement
The application includes a forward-declaration for a function named print_meta_data()
that prints the metadata found in a descriptor:
The following code specifies the column header information that the application uses when printing the metadata:
The following declaration section identifies the host variables to contain the name of the database the application connects to, the content of the SQL statement, and a host variable for the number of columns in the result set (if any).
The application connects to the database using the default credentials:
Next, the application allocates a SQL descriptor to hold the metadata for a statement:
The application uses a PREPARE
statement to check the syntax of the string provided by the user:
It also uses a DESCRIBE
statement to move the metadata for the query into the SQL descriptor.
Then, the application interrogates the descriptor to discover the number of columns in the result set and stores that in the host variable col_count
.
If the column count is zero, the end user didn't enter a SELECT
statement. The application uses an EXECUTE IMMEDIATE
statement to process the contents of the statement:
If the statement executes successfully, the application performs a COMMIT
:
If the statement entered by the user is a SELECT
statement (which we know because the column count is non-zero), the application declares a variable named row
:
Then, the application allocates another descriptor that holds the description and the values of a specific row in the result set:
The application declares and opens a cursor for the prepared statement:
It loops through the rows in the result set:
Then, it uses a FETCH
to retrieve the next row from the cursor into the descriptor:
The application confirms that the FETCH
didn't fail. If the FETCH
fails, the application reached the end of the result set and breaks the loop:
The application checks to see if this is the first row of the cursor. If it is, the application prints the metadata for the row:
Next, it prints a record header containing the row number:
Then, it loops through each column in the row:
The application interrogates the row descriptor (row_desc)
to copy the column value :val
, null indicator :ind
, and column name :name
into the host variables declared earlier. You can retrieve multiple items from a descriptor using a comma-separated list:
If the null indicator (ind
) is negative, the column value is NULL
. If the null indicator is greater than 0
, the column value is too long to fit into the val host variable, so we print <truncated>
. Otherwise, the null indicator is 0
, meaning NOT NULL
, so we print the value. In each case, we prefix the value (or <null>
or <truncated>
) with the name of the column.
When the loop terminates, the application prints the number of rows fetched and exits:
The print_meta_data()
function extracts the metadata from a descriptor and prints the name, data type, and length of each column:
The application declares host variables:
The application then defines an array of character strings that map data type values (numeric
) into data type names. We use the numeric value found in the descriptor to index into this array. For example, if we find that a given column is of type 2
, we can find the name of that type (NUMERIC
) by writing types[2]
.
The application retrieves the column count from the descriptor. The program refers to the descriptor using a host variable (desc
) that contains the name of the descriptor. In most scenarios, you use an identifier to refer to a descriptor. In this case, the caller provided the descriptor name, so we can use a host variable to refer to the descriptor.
The application prints the column headers defined at the beginning of this application:
Then, it loops through each column found in the descriptor and prints the name, type, and length of each column.
It retrieves the name, type code, and length of the current column:
If the numeric type code matches a 'known' type code (that is, a type code found in the types[]
array), it sets type_name
to the name of the corresponding type. Otherwise, it sets type_name
to "unknown"
:
It then prints the column number, name, type name, and length: