© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
W. D. AshleyFoundation Db2 and Pythonhttps://doi.org/10.1007/978-1-4842-6942-8_8

8. Python and ibm_db

W. David Ashley1  
(1)
Austin, TX, USA
 
In order to access Db2 from Python, you will need to download and set up the ibm_db module. Currently, this module is not available through any Linux, Unix, or Windows code repositories. However, it can be installed through the Python pip system or downloaded as source and compiled on your system. We highly recommend that you use pip to install the ibm_db module. But before you install it, there are some prerequisites that need to be in place before you can successfully perform the install:
  1. 1.

    Your system must be capable of compiling the source code. The C compiler and linker must be installed in order to create the module. On Linux you can check that the compiler is installed by running the following command:

     
gcc -v
  1. 2.
    If pip is not installed, then install it using your distribution’s package installer. Do not be concerned about Python versions of pip as the install will create a link to the proper version of pip for your version of Python:
    sudo dnf install pip
    or
    sudo yum install pip
     
  2. 3.
    If you are running on Linux or Unix, the python-devel package must be installed. Use your distribution’s package installer to install the python-devel (or python3-devel) package:
    sudo dnf install python-devel
    or
    sudo yum install python-devel
     
Once you have the perquisites installed, you can install the ibm_db package:
sudo pip install ibm_db
or
sudo python3 pip3 install ibm_db          
(for RHEL or CentOS)

This will install the Db2 package so that it will be available to your Python scripts. You do not need to install the Db2 client software unless you need it for other purposes. The ibm_db module will allow a Python script/program to access Db2 either locally or remotely without any other software packages.

The ibm_db package contains two modules that can be imported into your scripts: the ibm_db module, which is the standard way to access Db2 from Python, and the ibm_db_dbi module, which conforms to the Python DB-API 2.0 standard for accessing database systems generically. In this book, we will concentrate on the ibm_db module because that module is the base module for accessing Db2 and is actually used by the ibm_db_dbi module to perform all the work to access Db2.

The ibm_db module is a C source code–based module. It is open source and can be found at http://github.com/ibmdb/python-ibmdb/. Also, the Python tests for the module are available at the same location as well as the Python source code for the ibm_db_dbi module.

If you get error messages when you try to install the ibm_db package, then one or more of the prerequisites are not installed. If the message claims the Python.h file cannot be found, then you have not installed the python-devel package from the Linux/Unix distribution’s code repository, or it is not installed properly.

Once the ibm_db package is successfully installed, you are ready to write your first Python script to access Db2.

What follows are some examples of using the ibm_db module to retrieve data from the sample database. All these examples prompt you for a username and password. These values are always those that exist on the Db2 server, not on your local machine (unless that is where the Db2 system resides). If the sample database was installed on the server with the default options, then the username will be db2inst1, and the password will be whatever was set by the database administrator.

Your First Python ibm_db Program

The sample Python code in this chapter accesses the sample database, which comes with Db2. You will need to install the sample database or have your Db2 administrator install it for you. If you are installing it yourself on your own system, there is only one command you need to run to get it installed. Be sure to run this command as the db2inst1 account:
db2sampl -force -sql
This will take a little time to install, so be patient. Once installed, you can run the following commands to test that the database has been successfully installed:
db2 connect to sample
db2 "select * from department"

This should display the department table, and it should contain 14 records.

Once this executes successfully, you are now ready to write your first Python programs that access the Db2 sample database.

Our first Python program/script is actually very simple. It emulates the SQL select command we used previously to test the sample database. We want a nicely formatted display of the table contents, and we want to perform all the error checking needed to make sure we can track any errors the program may encounter. This will require a rather longer program than a first example usually requires, but it also gives us a chance to describe a number of ibm_db APIs that you will be using in all your Python programs.

The first Python example program follows in Listing 8-1.
#!/usr/bin/python
import sys, getpass
import ibm_db
def getColNamesWidths(results):
    # get the width of each column
    columns = list()
    col = 0
    numColumns = 0
    try:
        numColumns = ibm_db.num_fields(results)
    except Exception:
        pass
    # If information about the number columns returned could not be obtained,
    # display an error message and exit .
    if numColumns is False:
        print(" ERROR: Unable to obtain information about the result set produced.")
        conn.closeConnection()
        exit(-1)
    while col < numColumns:
        col_name = ibm_db.field_name(results, col)
        col_width = ibm_db.field_width(results, col)
        # the field name can be bigger than the display width
        col_width = max(len(col_name), col_width)
        columns.append((col_name, col_width))
        col += 1
    return columns # return a list of tuples (name, size)
def populateColTitleLines(columns):
    # populate the two title lines for the results
    col = 0
    line = ''
    lines = []
    # do the title line
    while col < len(columns):
        (col_name, col_width) = columns[col]
        title = col_name + ((col_width - len(col_name)) * ' ')
        line += '  ' + title
        col += 1
    lines.append(line)
    # do the underlines
    col = 0
    line = ''
    while col < len(columns):
        (col_name, col_width) = columns[col]
        line += '  ' + (col_width * '-')
        col += 1
    lines.append(line)
    return lines # return the two title lines
def populateLines(results, headerLines):
    # print the data records
    lines = []
    record = ibm_db.fetch_tuple(results)
    while record is not False:
        line = ''
        col = 0
        numColumns = 0
        try:
            numColumns = ibm_db.num_fields(results)
        except Exception:
            pass
        # If information about the number columns returned could not be obtained,
        # display an error message and exit .
        if numColumns is False:
            print(" ERROR: Unable to obtain information about the result set produced.")
            conn.closeConnection()
            exit(-1)
        while col < numColumns:
            colstr = record[col]
            (name, col_width) = headerLines[col]
            coltype = ibm_db.field_type(results, col)
            if record[col] is None:
                line += '  -' + ((col_width - 1) * ' ')
            elif coltype in ("clob", "dbclob", "blob", "xml", "string"):
                # these are the string types
                line += '  ' + str(colstr) + ((col_width - len(colstr)) * ' ')
            else:
                # these are the numeric types, or at least close enough
                colstr = str(colstr)
                line += '  ' + ((col_width - len(colstr)) * ' ') + colstr
            col += 1
        lines.append(line)
        record = ibm_db.fetch_tuple(results)
    return lines
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# get the records from the database
sqlstmt = 'select * from department'
try:
    results = ibm_db.exec_immediate(connID, sqlstmt)
except Exception:
    pass
# If the sql statement could not be executed, display an error message and exit
if results is False:
    print(" ERROR: Unable to execute the SQL statement specified.")
    ibm_db.close(connID)
    exit(-1)
# fetch SQL results and format lines
headerLines = getColNamesWidths(results)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(results, headerLines)
selrecords = len(dataLines)
#print the result lines
for line in titleLines:
    print(line)
for line in dataLines:
    print(line)
# print the number of records returned
print('     ' + str(selrecords) + ' record(s) selected.')
ibm_db.close(connID)
exit(0)
Listing 8-1

Display the department Table

The Python script starts with the usual first line to notify the system that this is a Python script and then specifies the Python interpreter to be executed.

The next two lines are the Python import statements that are needed by the script:
import sys, getpass
import ibm_db

The only thing here that is out of the ordinary is the import of the ibm_db module.

The first function, getColNamesWidths(), obtains the column name and display width needed for each column from the result table. The call to the ibm_db.num_fields() function obtains the number of columns that the result set contains. Once we have that number, we loop over each column and call the ibm_db.field_name() and ibm_db.field_width() to obtain the column name and the column display width. These will be used to add a title to each column and set the column display width. These values are then returned to the caller.

The next function, populateColTitleLines(), creates two lines, which will be eventually printed to the screen. These lines are a column title line and a separator dashed line. It utilizes the values from the previous function getColNamesWidths() to figure out how to format these lines. Once the lines have been created, they are returned to the caller as a Python list.

The next function, populateLines(), creates the lines fetched from the department table and formats each record as a display line using information passed to the function. Each line created is added to a Python list and then returned to the caller. Each result set record is fetched via the ibm_db.fetch_tuple() function and then formatted from the information passed to the function.

The main program code immediately follows the populateLines() function. The first part of the main program code sets some values that will be used later when the program connects to the Db2 database. The driver variable is always the same in all your Python scripts that use the ibm_db module. The host variable is either the IP address or the DNS name of the server that holds the sample database. If the database is local on your machine, then it can also be the IP address 127.0.0.1. The db variable has the name of the database you want to connect to; in our case, this is sample. We leave the uid and pw variables uninitialized so we do not code the userid and password information into the program. The autocommit and connecttype variables are options that we are not using in this script.

The next lines obtain the uid and pw variable information from the user. We then call the ibm_db.connect() function to actually connect to Db2 and the database specified by the previous variables. If this function fails for any reason, the Python script will exit with an error message. This is a deliberate decision so that we do not have a lot of code built into the program to correct information supplied by the user or mistyped into the script.

The next two sections of code are where the real work is performed. The first section sets the SQL statement to be executed. In this case, we want to fetch all information about the department table. We then call ibm_db.exec_immediate() to execute the SQL statement. If the ibm_db.exec_immediate() failed, an error message will be displayed, and the program will exit with an error message. If it succeeds, we proceed to the next section of code.

The next section of code calls the three functions defined in our program to fetch and display the results of the SQL statement. These functions were described previously.

The last major section actually prints the column titles and the fetched data to the standard output.

The last section calls ibm_db.close() to close our session with the Db2 database and exit the script.

Whew! That was a lot of code, but it is important to note that we included all the necessary error checking and produced a nicely formatted report back to our program user. Also, this is code that can be used over and over again in almost any program you write that uses the select statement or as the basis for a much more complicated and bigger program. And that is exactly what we will do in our later examples. Most of the program will be reused without any changes, and only the SQL statement will be changed or code added to that section to support extended functionality.

The output of Listing 8-1 is provided in Listing 8-2.
$ ./example8-1.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
  DEPTNO  DEPTNAME                              MGRNO   ADMRDEPT  LOCATION
  ------  ------------------------------------  ------  --------  ---------
  A00     SPIFFY COMPUTER SERVICE DIV.          000010  A00       -
  B01     PLANNING                              000020  A00       -
  C01     INFORMATION CENTER                    000030  A00       -
  D01     DEVELOPMENT CENTER                    -       A00       -
  D11     MANUFACTURING SYSTEMS                 000060  D01       -
  D21     ADMINISTRATION SYSTEMS                000070  D01       -
  E01     SUPPORT SERVICES                      000050  A00       -
  E11     OPERATIONS                            000090  E01       -
  E21     SOFTWARE SUPPORT                      000100  E01       -
  F22     BRANCH OFFICE F2                      -       E01       -
  G22     BRANCH OFFICE G2                      -       E01       -
  H22     BRANCH OFFICE H2                      -       E01       -
  I22     BRANCH OFFICE I2                      -       E01       -
  J22     BRANCH OFFICE J2                      -       E01       -
    14 record(s) selected.
Listing 8-2

Output of Listing 8-1

Although there are some small differences between our output and the display from the test Db2 command from the beginning of the chapter, it is essentially the same. We deliberately spaced the columns with two spaces to better separate the columns visually. Records that contain NULL data have a single dash character to indicate the NULL value in the beginning character of the column.

At this point, we should point out that this book has an appendix devoted to documenting the ibm_db APIs. Although this information has been taken from the ibm_db documentation, we have added a few notes that we hope make the documentation clearer and enhance it to make your programs more readable.

Using Parameter Markers

Our next example is slightly more complicated. It uses what are known as parameter markers to allow dynamic substitution of Python variable information into an SQL statement. This means that the values for parameter markers come from outside the SQL statement. In order to make this work, we have to use some different ibm_db API calls to prepare the SQL statement, substitute the variables, and then execute the statement.

We will essentially be using the same code from our previous example, but one section of the code will be different. The modified code is shown in Listing 8-3.
              .
              .
              .
# get the records from the database
sqlstmt = 'SELECT projname, deptno FROM project WHERE deptno = ?'
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
    print("Unable to prepare the SQL statement.")
    exit(-1)
deptid = 'B01'
retcode = ibm_db.bind_param(prepstmt, 1, deptid, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
try:
    results = ibm_db.execute(prepstmt)
except Exception:
    pass
# If the sql statement could not be executed, display an error message and exit
if results is False:
    print(" ERROR: Unable to execute the SQL statement specified.")
    ibm_db.close(connID)
    exit(-1)
              .
              .
              .
Listing 8-3

The Modified Code

Remember, the code before this section and after it is the same as in Listing 8-1 with the exception of two Python statements, so be careful in recreating this code if you are not using the packaged code from Apress.

The SQL statement is pretty normal except for the last character in the statement. The question mark (?) identifies a parameter marker. This is where we will substitute a Python variable later in the code. An SQL statement can have as many parameter markers as you need and can contain data or even SQL keywords. In our case, it will contain the specific department number we are looking for.

The next line is a call to ibm_db.prepare(). This will parse the SQL statement and make note of any parameter markers. This must be done prior to substituting the data into the SQL statement. We then check the return code from ibm_db.prepare() to make sure it succeeded before we proceed.

The next statement sets the Python variable we will substitute into the SQL statement. Where this value comes from is entirely up to you. It could be an input parameter to the Python program, it could come from a file, or you could even prompt the user for the value. Just make sure you do the proper error checking to the value prior to using it.

The next statement calls ibm_db.bind_param() to bind the Python variable to the SQL statement. The first parameter to the function identifies the prepared statement output from the previous call to ibm_db.prepare(). The second parameter is the Python variable to be substituted (or bound) in the SQL statement. In this case, it is the department number to be used. We need this in case you have coded more than one parameter marker in the SQL statement. You will need a separate call to ibm_db.bind_param() for each parameter marker. The third parameter specifies whether the marker is an input, output, or input/output variable. The fourth parameter specifies the SQL type of variable that is being passed.

The next set of statements surround our call to ibm_db.execute() for error checking purposes. This function actually executes the prepared SQL statement. The code that follows checks to make sure that the execution succeeded.

After executing the SQL statement, the code after is our calls to the three functions and then printing the results of the SQL statement. This code is unchanged from our previous example.

It is highly recommended that you read the documentation for the ibm_db.prepare() function . There are a large number of parameter types to identify the SQL data types that you should become familiar with.

SQL statements with parameter markers are probably the most used statement types in programs. They are highly flexible and require only a little more code to accommodate in your Python code.

Listing 8-4 provides the output from this example.
$ ./example8-2.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
  PROJNAME                  DEPTNO
  ------------------------  ------
  WELD LINE PLANNING        B01
Listing 8-4

Output from Listing 8-3

Our SQL statement only specified the retrieval of two columns, and there is only one department with the designation B01. The report is small because there is only one department with the number specified, but it gets to the point about how parameter markers can be very useful.

More on Parameter Markers

The next example uses multiple parameter markers to create a selection of values that are used to query the project table. Basically, we want to list all the project names that have specified department numbers.

Again, we will be using the basic code from the first example in this project to produce the report while only showing the modified code (Listing 8-5).
              .
              .
              .
# get the records from the database
sqlstmt = 'SELECT projname, deptno FROM project WHERE deptno = ? OR deptno = ?'
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
    print("Unable to prepare the statement.")
    exit(-1)
deptid1 = 'B01'
deptid2 = 'D01'
retcode = ibm_db.bind_param(prepstmt, 1, deptid1, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
retcode = ibm_db.bind_param(prepstmt, 2, deptid2, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
              .
              .
              .
Listing 8-5

Using Multiple Parameter Values

Remember, the code before this section and after it is the same as in Listing 8-1 with the exception of two Python statements, so be careful in recreating this code if you are not using the packaged code from Apress.

The select statement for this query contains two parameter values. The first marker will be labeled as 1; the second is labeled as 2. Parameter markers are always labeled from left to right in the SQL statement.

Next, we prepare the SQL statement with a call to ibm_db.prepare() just as all SQL statements with parameter markers need to be. If the call returns False, then we produce an error message and exit the Python program.

The assignments to Python variables that will be substituted into the parameter markers are next in the listing. Again, these can come from anywhere. We assign with them values here just to make what is happening as clear as possible.

Now we call ibm_db.bind_param() twice, once for each parameter marker. Once the value is bound to the SQL prepared statement, it remains bound until another call to ibm_cb.bind_param() is made.

Finally, we call ibm_db.execute() to obtain the query result.

Listing 8-6 shows the output of this query.
$ ../examples/example8-3.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
  PROJNAME                  DEPTNO
  ------------------------  ------
  ADMIN SERVICES            D01
  WELD LINE AUTOMATION      D01
  WELD LINE PLANNING        B01
    3 record(s) selected.
Listing 8-6

Output from Listing 8-5

The report shows that we have three projects that have the specified department numbers we used as parameter markers, two projects for department 'D01' and one for department ‘B01’. This is another good example of how parameter markers can be very useful in your programs. You can store the needed data in a file or feed it as parameters to the Python program and change the report without any modifications to this Python script.

Producing Multiple Reports with Parameter Markers

The Listing 8-7 shows how to create multiple reports with a single SQL statement with parameter markers. As you will see, this is actually easier than it sounds.

Again, we will be using the basic code from the first example in this project to produce the report while only showing the modified code.
              .
              .
              .
# get the records from the database
sqlstmt = """SELECT empno, firstnme, midinit, lastname, salary
 FROM employee WHERE salary < ? and salary > ?
 ORDER BY salary"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
    print("Unable to prepare the statement.")
    exit(-1)
salaries = (('35000.00', '0.00'), ('50000.00', '40000.00'))
for i in (salaries):
    (salary1, salary2) = i
    retcode = ibm_db.bind_param(prepstmt, 1, salary1, ibm_db.SQL_PARAM_INPUT,
                                ibm_db.SQL_CHAR)
    retcode = ibm_db.bind_param(prepstmt, 2, salary2, ibm_db.SQL_PARAM_INPUT,
                                ibm_db.SQL_CHAR)
    results = ibm_db.execute(prepstmt)
    headerLines = getColNamesWidths(prepstmt)
    titleLines = populateColTitleLines(headerLines)
    dataLines = populateLines(prepstmt, headerLines)
    selrecords = len(dataLines)
    #print the result lines
    for line in titleLines:
        print(line)
    for line in dataLines:
        print(line)
    # print the number of records returned
    print('     ' + str(selrecords) + ' record(s) selected. ')
              .
              .
              .
Listing 8-7

Producing Multiple Reports with One SQL Statement

At first glance, this listing looks a little different from our other examples. The main reason is that we have moved the report printing code into the loop. We do this because we need a report printed for each invocation of the ibm_db.execute() function .

Our SQL statement is slightly more complicated because we have added an ORDER BY clause at the end so that the output is ordered. It still has two parameter markers that specify a range of salaries.

Next, we prepare the SQL statement and test the outcome for errors.

The next statement sets up a Python array of lists that specify the range of salaries we want. There are two ranges specified, and each range will be used in separate invocations of the ibm_db.execute() function .

The rest of the code is a loop that takes a list of salary values to query the employee table. The code works just like the previous examples except that the report printing code has been moved inside the loop so that both queries are able to print their report.

One of the things to note in this example is that we did not have to prepare the SQL statement multiple times. This is by design so that the prepared SQL statement can be used multiple times just as our example does. Rebinding new values to the parameter markers and then executing the statement are all that needs to be done to produce possibly different reports.

Listing 8-8 provides the output from this example.
$ ../examples/example8-4.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
  EMPNO   FIRSTNME      MIDINIT  LASTNAME         SALARY
  ------  ------------  -------  ---------------  -----------
  200340  ROY           R        ALONZO              31840.00
    1 record(s) selected.
  EMPNO   FIRSTNME      MIDINIT  LASTNAME         SALARY
  ------  ------------  -------  ---------------  -----------
  000230  JAMES         J        JEFFERSON           42180.00
  000340  JASON         R        GOUNOT              43840.00
  000170  MASATOSHI     J        YOSHIMURA           44680.00
  000330  WING                   LEE                 45370.00
  200280  EILEEN        R        SCHWARTZ            46250.00
  200010  DIAN          J        HEMMINGER           46500.00
  000260  SYBIL         P        JOHNSON             47250.00
  000240  SALVATORE     M        MARINO              48760.00
  000250  DANIEL        S        SMITH               49180.00
  000120  SEAN                   O'CONNELL           49250.00
  000220  JENNIFER      K        LUTZ                49840.00
    11 record(s) selected.
Listing 8-8

Multiple Reports from a Single SQL Statement

We have successfully produced two reports on two different salary ranges and ordered the records by the salary field. The only change we made to the report printing code was to add a newline character at the end of the report so the two reports have some spacing between them.

Using Parameter Markers Without Binding Variables

This example is best used when you have a lot of data to load or update into a table. The ibm_db.execute() function has an additional optional parameter that can be used to pass parameter marker values instead of calling the ibm_db.bind_param() function for each parameter marker.

This use of ibm_db.execute() works best when the rows to be inserted/updated come from a file or another external resource. In our example, we use a file that contains rows to be inserted into the employee table. The file is constructed as a comma-separated values (CSV) file with each row of data representing a new row to be added to the employee table.

The added problem to be solved in this example is converting each row of data from the input file from a single string to a set of values for a Python tuple, which is what is required by the ibm_db.execute() function . We will go into this in more detail after the Listing 8-9 is shown.
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Add new designer employees to the employee table
sql = """INSERT INTO employee (empno, firstnme, midinit, lastname,
         workdept, phoneno, hiredate, job, edlevel, sex, birthdate,
         salary, bonus, comm) VALUES
         (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""
stmt = ibm_db.prepare(connID, sql)
if stmt:
    inserts = 0
    with open('./example8-5.csv') as f:
        line = f.readline()
        while len(line) > 0:
            emp_list = line.split(',')
            for i in range(0, len(emp_list)):
                emp_list[i] = emp_list[i].rstrip("' ")
                emp_list[i] = emp_list[i].lstrip("' ")
            emp = tuple(emp_list)
            result = ibm_db.execute(stmt, emp)
            if result is False:
                print("Unable to execute the SQL statement.")
                exit(-1)
            inserts += 1
            line = f.readline()
    print(str(inserts) + ' employees inserted successfully.')
# Now delete those new employees
ibm_db.exec_immediate(connID, "delete from employee where empno = '000350'")
ibm_db.exec_immediate(connID, "delete from employee where empno = '000360'")
ibm_db.exec_immediate(connID, "delete from employee where empno = '000370'")
ibm_db.exec_immediate(connID, "delete from employee where empno = '000380'")
print('4 employees deleted successfully.')
ibm_db.close(connID)
exit(0)
Listing 8-9

Parameter Markers Without Binding Variables

The first statements are the standard Python import statements needed for our program.

The next section of code is our standard setup for accessing a Db2 database, prompting the user for a userid and password, and connecting to the Db2 database.

The next section of code is different from our previous examples. We set up an SQL INSERT statement with parameter markers for each column of data we will insert. In this case, that happens to be all the columns in the employee table. Next, we prepare the statement for eventual execution.

The next block of code is where all the work happens. We open the file containing the information to be inserted into the table first. Then we read it in a line at a time. Next, since the file is a CSV construct, we split the line at each comma. The split pieces have data we do not need in each fragment, so we eliminate the leading and trailing spaces and the single quotes. Finally, we change the Python list of column values into a Python tuple.

Now we can call ibm_db.execute() with our tuple of column values as the second parameter in the function call. We then check to make sure that the insert works and then read in the next line.

The last thing we do is to delete the lines we added to the employee table. This will keep the database in its original state.

Last, we close the database connection.

Listing 8-10 shows all the output from the program. There is not much to see here, but this is what you should see as a result of the program executing successfully.
$ ./example8-5.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
4 employees inserted successfully.
4 employees deleted successfully.
Listing 8-10

Output from Listing 8-9

What this example shows is that you can eliminate 14 calls to the ibm_db.bind_param() function with a little work inside a loop. It also eliminates the use of 14 different variables to hold the information and replaces them with a simple Python list and a single tuple.

We should add another note here to possibly clear up an item. Tuples in Python are immutable. It means that once they are created, they cannot be modified or extended with additional members. That is why we had to convert the Python list, once it was ready, to a tuple in one call.

Joining Tables

Db2 supports the joining of tables in queries. There are two types of joins – inner and outer joins. Inner joins have been supported since the inception of Db2, but outer joins are a relatively new concept. We will discuss both types in this section.

Inner Joins

Inner joins have two forms in SQL. The first form of an inner join dates back to the beginning of Db2 and is rather straightforward to code. The second form of an inner join is just as easy to code and is much easier to determine exactly what is being joined and how it is being joined.

The following Listing 8-11 shows the old form of an inner join. This is only an excerpt of the entire program just to show the SQL statement and how it is processed.
# get the records from the database
sqlstmt = """SELECT e.empno, e.lastname, d.deptname FROM emp e, dept d
    WHERE e.workdept = d.deptno AND d.deptname = ?"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
    print("Unable to prepare the statement.")
    exit(-1)
deptnme = 'SOFTWARE SUPPORT'
retcode = ibm_db.bind_param(prepstmt, 1, deptnme, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
headerLines = getColNamesWidths(prepstmt)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(prepstmt, headerLines)
selrecords = len(dataLines)
Listing 8-11

The Old Form of an Inner Join

The output from this program will display only the employees working in the department known as “SOFTWARE SUPPORT.” There should only be a total of six employees in that department. The two tables are joined in the workdept column in the employee table and the deptno column in the department table. An additional constraint asks only to see rows where the deptname is equal to “SOFTWARE SUPPORT.”

As you can see, this example is pretty straightforward. Problems happen when the SQL statement has many constraints and the joined columns are not easily spotted in the SQL statement. To resolve this issue and to easily support outer joins, a new clause was added to the SQL statement that explicitly specifies the type of join.

The following Listing 8-12 shows the new syntax for an inner join, which obtains the same data as the previous example.
# get the records from the database
sqlstmt = """SELECT e.empno, e.lastname, d.deptname FROM emp e
    INNER JOIN dept d ON e.workdept = d.deptno AND d.deptname = ?"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
    print("Unable to prepare the statement.")
    exit(-1)
deptnme = 'SOFTWARE SUPPORT'
retcode = ibm_db.bind_param(prepstmt, 1, deptnme, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
headerLines = getColNamesWidths(prepstmt)
titleLines = populateColTitleLines(headerLines)
dataLines = populateLines(prepstmt, headerLines)
selrecords = len(dataLines)
Listing 8-12

The New Form of an Inner Join

The only thing modified in this example is the SQL statement. Otherwise, the program is unchanged. The SQL statement has a new clause – the INNER JOIN clause and the subclause ON. These explicitly state that an inner join is being made on the two tables specified. There is no WHERE clause as the ON clause replaces it in this case.

The output from this program is exactly the same as the previous program. The reason both examples are shown here is that there are still a large number of programs still using the old form of an inner join and it is important to recognize and deal with these programs as needed.

Outer Joins

What makes an outer join different from an inner join is its ability to not only show you result rows similar to the inner join but also rows where the join columns from both tables are set to NULL. Thus, it displays rows that can never be joined together because one of the columns has an invalid value.

This is actually a rarely used feature of Db2, and thus we will not attempt to show an example here. For more information, refer to the IBM Db2 Knowledge Center on the Web.

Inserts, Updates, and Deletes

In this section, we will look at SQL insert, update, and delete statements and how they relate to Python and parameter markers. We actually saw delete statements in the previous section’s example, but here we will discuss all these statements in more detail.

Listing 8-13 is an example of using all four SQL data manipulation statements. It inserts a new entry into the employee table, updates it, fetches it for display, and then deletes the entry. This will maintain the employee table in its original state.
#!/usr/bin/python
import sys, getpass
from decimal import *
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Add new designer employees to the employee table
sql = """INSERT INTO employee (empno, firstnme, midinit, lastname,
         workdept, phoneno, hiredate, job, edlevel, sex, birthdate,
         salary, bonus, comm) VALUES
         (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"""
stmt = ibm_db.prepare(connID, sql)
if stmt is False:
    print("Unable to prepare the SQL statement.")
    exit(-1)
emp = ('000350', 'DAVID', 'W', 'ANDREWS', 'D11', '3634', '1969-03-20',  
    'DESIGNER', 20, 'M', '1951-06-14', 40160.00,  500, 2220)
result = ibm_db.execute(stmt, emp)
if result is False:
    print("Unable to execute the SQL statement.")
    ibm_db.close(connID)
    exit(-1)
# Now update the salary
sql = "UPDATE employee SET salary = ? where empno = '000350'"
stmt = ibm_db.prepare(connID, sql)
if stmt is False:
    print("Unable to prepare the SQL statement.")
    exit(-1)
salary = str(Decimal('40160.00') * Decimal('1.1'))
retcode = ibm_db.bind_param(stmt, 1, salary, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
result = ibm_db.execute(stmt)
if result is False:
    print("Unable to execute the SQL statement.")
    ibm_db.close(connID)
    exit(-1)
# Ensure the salary is updated
sql = "select empno, salary from employee where empno = '000350'"
results = ibm_db.exec_immediate(connID, sql)
if results is False:
    print(" ERROR: Unable to execute the SQL statement specified.")
    ibm_db.close(connID)
    exit(-1)
(empno, salary) = ibm_db.fetch_tuple(results)
print('empno: ', str(empno), '  old salary: 40160.00  new salary: ', str(salary))
# Now delete the employee we added
ibm_db.exec_immediate(connID, "delete from employee where empno = '000350'")
ibm_db.close(connID)
exit(0)
Listing 8-13

Insert, Update, and Delete Example

The example listing starts out the same as all of our examples, with one exception. In order to update the salary, which internally is a decimal field, we need to import the decimal module. This way we can keep the adjustment consistent with the SQL field.

The first task is to insert a new record into the employee table. We use parameter fields in an INSERT statement. That is what all the question marks are in the SQL statement. We then call ibm_db.prepare() to prepare the statement.

Next, we create the tuple for the data to be inserted. Then, we call ibm_db.execute() to insert the new row into the database.

The next task is to update the record we just inserted. We create the SQL UPDATE statement with a single parameter marker for the new salary and then prepare the statement. The next statement converts the current salary to a decimal field as well as increases it by 10%. The result of this is a new field that is also a decimal field. We then bind the new salary to the UPDATE SQL statement and execute the statement.

Just to make sure the update succeeded and to prove to ourselves that it worked, we fetch the data from the database with a SELECT statement and display the results. The results display the employee number, the old salary, and the new salary.

Last, we delete the new row to get the database back to its original starting state.

This example is a little convoluted, but it is an example of all the SQL data manipulation statements. This example can also be used as a starting point for many of the types of activities you will encounter almost every day.

Listing 8-14 has the output from Listing 8-13. It shows the salary of the new employee both before and after the update.
$ ./example8-6.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
empno:  000350   old salary: 40160.00  new salary:  44176.00
Listing 8-14

Output from Listing 8-13

It is important to note that the calculation of the 10% increase is exactly correct and does not suffer from the inaccuracies from a floating-point calculation. By using the decimal package to perform the calculation, we have ensured the correct increase was applied.

Some Other ibm_db APIs

In this section, we will look at some other ibm_db package APIs that you may have occasion to use. These are not data manipulation statements, but instead can provide information about various aspects of the database and the processing environment of your Python program.

It should be noted that this does not cover all the remaining APIs in the ibm_db package. The remaining APIs are rarely used and are sufficiently documented so they can be easily incorporated in your programs.

Listing 8-15 shows the code for this example. We will examine it after the listing.
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Test if the connection is active
active = ibm_db.active(connID)
if active:
    print('The currect connection is active.')
else:
    print('*The current connection is not active.')
# Test autocommit
commit = ibm_db.autocommit(connID)
if active:
    print('Autocommit is active.')
else:
    print('*Autocommit is not active.')
# Get the client info
clientinfo = ibm_db.client_info(connID)
if clientinfo:
    print('Client info:')
    print('  APPL_CODEPAGE: ', clientinfo.APPL_CODEPAGE)
    print('  CONN_CODEPAGE: ', clientinfo.CONN_CODEPAGE)
    print('  DATA_SOURCE_NAME: ', clientinfo.DATA_SOURCE_NAME)
    print('  DRIVER_NAME: ', clientinfo.DRIVER_NAME)
    print('  DRIVER_ODBC_VER: ', clientinfo.DRIVER_ODBC_VER)
    print('  DRIVER_VER: ', clientinfo.DRIVER_VER)
    print('  ODBC_SQL_CONFORMANCE: ', clientinfo.ODBC_SQL_CONFORMANCE)
    print('  ODBC_VER: ', clientinfo.ODBC_VER)
else:
    print('Could not obtain client info.')
# Get column priviliges, if they exist
priv = ibm_db.column_privileges(connID, None, uid.upper(), 'employee', 'workdept')
row = ibm_db.fetch_assoc(priv)
if row:
    print('Sample database, table department, column priviliges:')
    print("  Schema name            : {}" .format(row['TABLE_SCHEM']))
    print("  Table name             : {}" .format(row['TABLE_NAME']))
    print("  Column name            : {}" .format(row['COLUMN_NAME']))
    print("  Privilege grantor      : {}" .format(row['GRANTOR']))
    print("  Privilege recipient    : {}" .format(row['GRANTEE']))
    print("  Privilege              : {}" .format(row['PRIVILEGE']))
    print("  Privilege is grantable : {}" .format(row['IS_GRANTABLE']))
else:
    print('No column privileges to retrieve.')
# Get column metadata, if it exists
coldata = ibm_db.columns(connID, None, None, 'employee', 'empno')
row = ibm_db.fetch_assoc(coldata)
if row:
    print('Sample database, table department, columns metadata:')
    table_name = row['TABLE_NAME']
    column_name = row['COLUMN_NAME']
    print("  Table name   : {}" .format(table_name))
    print("    Column name  : {}" .format(column_name))
else:
    print('No column metadata to retrieve.')
# Test SQL commit
rc = ibm_db.commit(connID)
if rc:
    print('Commit succeeded.')
else:
    print('*Commit did not succeed.')
ibm_db.close(connID)
exit(0)
Listing 8-15

Some Miscellaneous ibm_db APIs

As always, the program has the same starting code as our other examples. The first test is the ibm_db.active() API. This API determines if the connection passed as a parameter is still active. While not usually important in most programs, it is used in programs that use the ibm_db.pconnect() API.

The next API used is the ibm_db.autocommit(). This API can both get and set the AUTOCOMMIT flag for the specified connection.

The next API is the ibm_db.client_info(). This API returns information about the client side of the connection. It lists the code pages used, the driver names and versions, and the SQL standard conformance.

Next, the ibm_db.column_priviliges() queries tables about the privileges assigned to specific columns. These privileges may or may not exist. The tables in the sample database usually do not have special privileges assigned to them, so the API does not return any data from them.

The ibm_db.columns() API is used to query the metadata that has been assigned to a column in a table. The sample database has no metadata assigned to columns that we have been able to determine.

The last API we test is the ibm_db.commit() API. This API forces a COMMIT to the database at the point it is called. This should work whether the AUTOCOMMIT flag is on or not.

Listing 8-16 shows the output of Listing 8-15 on an example client machine.
$ ./example8-7.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
The currect connection is active.
Autocommit is active.
Client info:
  APPL_CODEPAGE:  1208
  CONN_CODEPAGE:  1208
  DATA_SOURCE_NAME:  SAMPLE
  DRIVER_NAME:  libdb2.a
  DRIVER_ODBC_VER:  03.51
  DRIVER_VER:  11.01.0405
  ODBC_SQL_CONFORMANCE:  EXTENDED
  ODBC_VER:  03.01.0000
No column privileges to retrieve.
No column metadata to retrieve.
Commit succeeded.
Listing 8-16

Output from the Listing 8-15 Program

This output from is pretty simple. Mostly, just one line results from the API being tested. The client info API has more data, all of it of interest to the programmer who requires the information.

Creating Database Objects

The ibm_db library can also help you create database objects such as tables, indexes, and tablespaces. It can also remove existing database objects when necessary. These actions are done through the CREATE and DROP SQL statements.

In addition, this section will cover two more ibm_db APIs, the stmt_error and the stmt_errormsg. These APIs are used to report error conditions and explanations. They can be used after the prepare(), exec_immediate(), and callproc() APIs and contain information that will help you to diagnose the problem with your SQL statement.

Listing 8-17 shows how to create a table using the Python module. A word of note: The example that follows has no real meaning but is used strictly for showing examples of table column definitions.
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# create the sample table
sqlstmt = """CREATE TABLE myexampletable (
    C01        INTEGER NOT NULL
               GENERATED ALWAYS AS IDENTITY
               (START WITH 1, INCREMENT BY 1),
    C02        CHAR(50),
    C03        VARCHAR(70),
    C04        DEC(15,2),
    C05        FLOAT(21),
    C06        CLOB(1K),
    C07        VARGRAPHIC(2000),
    C08        BLOB(1M),
    C09        DATE,
    C10        TIME,
    C11        TIMESTAMP,
    C12        XML,
    C13        BOOLEAN,
    PRIMARY KEY(C01))"""
try:
    rc = ibm_db.exec_immediate(connID, sqlstmt)
except:
    print("Create ' {} ' failed with ".format(sqlstmt))
    print("Error : {}".format(ibm_db.stmt_errormsg()))
    exit(-1)
print('     The CREATE statement executed successfully.')
ibm_db.close(connID)
exit(0)
Listing 8-17

Creating a Sample Table

There are a few things to note about this example. First, the table is meaningless except as an example. It is not tied to any other table in the sample database. It is just an example of some of the column data types available to the user by Db2.

Second, the try/except block adds some nice exception processing. It looks for a non-zero return code from the exec_immidiate() API, and if not found it executes the except block of code.

Third, the except block has a call to the stmt_errormsg() API, which will print out the error message associated with the error in the SQL statement.

Fourth, there is no formatting code in this example because it does not return a result table, just a simple return code.

The C01 column is an example of an incrementing column. Each time a new row is inserted or altered, the database will generate a value to be placed in this column. This column is also the primary key for the table as defined by the last clause in the SQL statement.

The C02 is a character string that always has 50 characters associated with it. When you assign a character string to this column that is shorter than 50 characters, the system will pad the string on the right with blanks until it is 50 characters long. If you try to add a string longer than 50 characters, it will be truncated to 50 characters.

The C03 column is a variable character string. This type of string is not padded on the right with blanks. Instead, the actual length of the string is recorded. But if the string is longer than 70 characters, it will be truncated to 70 characters.

The C04 column is a fixed decimal number with a length of 15 numeric values with 2 decimal places.

The C05 column is a floating-point field that can hold a total of 21 numeric characters.

The C06 column is a variable graphic field with a maximum of 1,000,000 bytes.

The C07 column is a variable graphic field with a maximum of 200 bytes.

The C08 column is a binary LOB with a length of 1,000,000 bytes.

The C09 column is a field that contains a data value.

The C10 column is a field that contains a time value.

The C11 column is a field that contains a timestamp value. This field contains both a date and a time value concatenated together to form an instance of time and date.

The C12 column is an XML field. This field actually is a pointer to the file system that contains the XML data.

The C13 column is a Boolean field for simple yes/no values.

Now that we have looked at this table, you may want to remove it from the database. Listing 8-18 will do that for you.
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, autocommit, connecttype)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# create the sample table
sqlstmt = """DROP TABLE myexampletable"""
try:
    rc = ibm_db.exec_immediate(connID, sqlstmt)
except:
    print("Drop ' {} ' failed with ".format(sqlstmt))
    print("Error : {}".format(ibm_db.stmt_errormsg()))
    exit(-1)
print('     The DROP statement executed successfully.')
ibm_db.close(connID)
exit(0)
Listing 8-18

Removing a Table

This program is very similar to the previous one except that it uses the DROP SQL statement to remove the table we created at the beginning of this section.

Obtaining Attributes of an Existing Table

Sometimes you may need to know the attributes of each column in an existing Db2 table. With the ibm_db library , you can easily obtain these attributes. Listing 8-19 shows how to accomplish this.
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
resultSet = False
dataRecord = False
tableName = "EMP"
sqlDataTypes = {0 : "SQL_UNKNOWN_TYPE", 1 : "SQL_CHAR", 2 : "SQL_NUMERIC", 3 : "SQL_DECIMAL",
    4 : "SQL_INTEGER", 5 : "SQL_SMALLINT", 6 : "SQL_FLOAT", 7 : "SQL_REAL", 8 : "SQL_DOUBLE",
    9 : "SQL_DATETIME", 12 : "SQL_VARCHAR", 16 : "SQL_BOOLEAN", 19 : "SQL_ROW",
   91 : "SQL_TYPE_DATE", 92 : "SQL_TYPE_TIME", 93 : "SQL_TYPE_TIMESTAMP",
   95 : "SQL_TYPE_TIMESTAMP_WITH_TIMEZONE", -8 : "SQL_WCHAR", -9 : "SQL_WVARCHAR",
  -10 : "SQL_WLONGVARCHAR", -95 : "SQL_GRAPHIC", -96 : "SQL_VARGRAPHIC",
  -97 : "SQL_LONGVARGRAPHIC", -98 : "SQL_BLOB", -99 : "SQL_CLOB", -350 : "SQL_DBCLOB",
 -360 : "SQL_DECFLOAT", -370 : "SQL_XML", -380 : "SQL_CURSORHANDLE", -400 : "SQL_DATALINK",
 -450 : "SQL_USER_DEFINED_TYPE"}
sqlDateTimeSubtypes = {1 : "SQL_CODE_DATE", 2 : "SQL_CODE_TIME", 3 : "SQL_CODE_TIMESTAMP",
    4 : "SQL_CODE_TIMESTAMP_WITH_TIMEZONE"}
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, '', '')
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Attempt to retrieve information about all columns of a table
resultSet = ibm_db.columns(connID, None, None, tableName, None)
# If The Information Desired Could Not Be Retrieved, Display An Error Message And Exit
if resultSet is False:
    print(" ERROR: Unable to obtain the information desired .")
    conn.closeConnection()
    exit(-1)
noData = False
loopCounter = 1
while noData is False:
    dataRecord = ibm_db.fetch_assoc(resultSet)
    if dataRecord is False:
        noData = True
    else:
        # Display Record Header Information
        print("Column " + str(loopCounter) + " details:")
        print("_________________________________________")
        # Display The Information Stored In The Data Record Retrieved
        print("Table schema             : {}" .format(dataRecord['TABLE_SCHEM']))
        print("Table name               : {}" .format(dataRecord['TABLE_NAME']))
        print("Column name              : {}" .format(dataRecord['COLUMN_NAME']))
        print("Data type                : {}" .format(dataRecord['TYPE_NAME']))
        print("Size                     : {}" .format(dataRecord['COLUMN_SIZE']))
        print("Buffer size              : {}" .format(dataRecord['BUFFER_LENGTH']))
        print("Scale (decimal digits)   : ", end="")
        if dataRecord['DECIMAL_DIGITS'] == None:
            print("Not applicable")
        else:
            print("{}" .format(dataRecord['DECIMAL_DIGITS']))
        print("Precision radix          : ", end="")
        if dataRecord['NUM_PREC_RADIX'] == 10:
            print("Exact numeric data type")
        elif dataRecord['NUM_PREC_RADIX'] == 2:
            print("Approximate numeric data type")
        elif dataRecord['NUM_PREC_RADIX'] == None:
            print("Not applicable")
        print("Can accept NULL values   : ", end="")
        if dataRecord['NULLABLE'] == ibm_db.SQL_FALSE:
            print("NO")
        elif dataRecord['NULLABLE'] == ibm_db.SQL_TRUE:
            print("YES")
        print("Remarks                  : {}" .format(dataRecord['REMARKS']))
        print("Default value            : {}" .format(dataRecord['COLUMN_DEF']))
        print("SQL data type            : ", end="")
        print(sqlDataTypes.get(dataRecord['SQL_DATA_TYPE']))
        print("SQL data/time subtype    : ", end="")
        print(sqlDateTimeSubtypes.get(dataRecord['SQL_DATETIME_SUB']))
        print("Data type                : {}" .format(dataRecord['DATA_TYPE']))
        print("Length in octets         : ", end="")
        if dataRecord['CHAR_OCTET_LENGTH'] == None :
            print("Not applicable")
        else:
            print("{}" .format(dataRecord['CHAR_OCTET_LENGTH']))
        print("Ordinal position         : {}" .format(dataRecord['ORDINAL_POSITION']))
        print("Can contain NULL values  : {}" .format(dataRecord['IS_NULLABLE']))
        # Increment The loopCounter Variable And Print A Blank Line To Separate The
        # Records From Each Other
        loopCounter += 1
        print()
ibm_db.close(connID)
exit(0)
Listing 8-19

Obtaining the Column Attributes of an Existing Table

There are a number of attributes available for a column. Some of these will not be applicable depending on the data type defined on the column or through not being defined. The main attribute you will be interested in is the DATA TYPE or the SQL DATA TYPE, which determines the kind of data stored in the column. There are specific attributes that only apply to certain types of data, that is, the SCALE attribute for instance only applies to the DECIMAL data type.

Listing 8-20 lists all the columns in a table. In the following output, we have eliminated some of the columns in order to save space.
$  ./example8-14.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
Column 1 details:
_________________________________________
Table schema             : DB2INST1
Table name               : EMP
Column name              : EMPNO
Data type                : CHAR
Size                     : 6
Buffer size              : 6
Scale (decimal digits)   : Not applicable
Precision radix          : Not applicable
Can accept NULL values   : NO
Remarks                  : None
Default value            : None
SQL data type            : SQL_CHAR
SQL data/time subtype    : None
Data type                : 1
Length in octets         : 6
Ordinal position         : 1
Can contain NULL values  : NO
Column 2 details:
_________________________________________
Table schema             : DB2INST1
Table name               : EMP
Column name              : FIRSTNME
Data type                : VARCHAR
Size                     : 12
Buffer size              : 12
Scale (decimal digits)   : Not applicable
Precision radix          : Not applicable
Can accept NULL values   : NO
Remarks                  : None
Default value            : None
SQL data type            : SQL_VARCHAR
SQL data/time subtype    : None
Data type                : 12
Length in octets         : 12
Ordinal position         : 2
Can contain NULL values  : NO
.
.
.
Column 14 details:
_________________________________________
Table schema             : DB2INST1
Table name               : EMP
Column name              : COMM
Data type                : DECIMAL
Size                     : 9
Buffer size              : 11
Scale (decimal digits)   : 2
Precision radix          : Exact numeric data type
Can accept NULL values   : YES
Remarks                  : None
Default value            : None
SQL data type            : SQL_DECIMAL
SQL data/time subtype    : None
Data type                : 3
Length in octets         : Not applicable
Ordinal position         : 14
Can contain NULL values  : YES
Listing 8-20

Output of Listing 8-19

Each column along with all attributes is shown in the complete output file. Listing 8-20 is just a small excerpt of the complete listing.

The Listing 8-14 Python program is very useful in a number of circumstances such as discovering if NULLs are allowed in a column, if there is a DEFAULT VALUE specified for a column, the SIZE attribute that can provide a clue as to the maximum size of a column, and many other attributes.

Obtaining Attributes of a Result Set

Unlike obtaining the attributes of an existing Db2 table, a result set is a temporary table that holds the result of a query. The reason we have this API is that a result set may be a joining of two or more tables and some columns may be modified in the join in such a way that their attributes are modified by the join process. Thus, it may be hard to determine the actual attributes on a joined column except thru trial and error.

Listing 8-21 demonstrates how to discover the attributes for a result set so that they can be used to help you determine how a column should be displayed.
#!/usr/bin/python
import sys, getpass
import ibm_db
# main program
resultSet = False
dataRecord = False
tableName = "EMP"
sqlDataTypes = {0 : "SQL_UNKNOWN_TYPE", 1 : "SQL_CHAR", 2 : "SQL_NUMERIC", 3 : "SQL_DECIMAL",
    4 : "SQL_INTEGER", 5 : "SQL_SMALLINT", 6 : "SQL_FLOAT", 7 : "SQL_REAL", 8 : "SQL_DOUBLE",
    9 : "SQL_DATETIME", 12 : "SQL_VARCHAR", 16 : "SQL_BOOLEAN", 19 : "SQL_ROW",
   91 : "SQL_TYPE_DATE", 92 : "SQL_TYPE_TIME", 93 : "SQL_TYPE_TIMESTAMP",
   95 : "SQL_TYPE_TIMESTAMP_WITH_TIMEZONE", -8 : "SQL_WCHAR", -9 : "SQL_WVARCHAR",
  -10 : "SQL_WLONGVARCHAR", -95 : "SQL_GRAPHIC", -96 : "SQL_VARGRAPHIC",
  -97 : "SQL_LONGVARGRAPHIC", -98 : "SQL_BLOB", -99 : "SQL_CLOB", -350 : "SQL_DBCLOB",
 -360 : "SQL_DECFLOAT", -370 : "SQL_XML", -380 : "SQL_CURSORHANDLE", -400 : "SQL_DATALINK",
 -450 : "SQL_USER_DEFINED_TYPE"}
sqlDateTimeSubtypes = {1 : "SQL_CODE_DATE", 2 : "SQL_CODE_TIME", 3 : "SQL_CODE_TIMESTAMP",
    4 : "SQL_CODE_TIMESTAMP_WITH_TIMEZONE"}
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db.connect(conn_str, '', '')
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# Attempt to retrieve information about all columns of a table
sqlstmt = """SELECT e.empno, e.lastname, d.deptname FROM emp e, dept d
    WHERE e.workdept = d.deptno AND d.deptname = ?"""
prepstmt = ibm_db.prepare(connID, sqlstmt)
if prepstmt is False:
    print("Unable to prepare the statement.")
    exit(-1)
deptnme = 'SOFTWARE SUPPORT'
retcode = ibm_db.bind_param(prepstmt, 1, deptnme, ibm_db.SQL_PARAM_INPUT,
                            ibm_db.SQL_CHAR)
results = ibm_db.execute(prepstmt)
# If The Information Desired Could Not Be Retrieved, Display An Error Message And Exit
if results is False:
    print(" ERROR: Unable to obtain the information desired .")
    ibm_db.close(connID)
    exit(-1)
loopCounter = 1
cols = ibm_db.num_fields(prepstmt)
while loopCounter <= cols:
    # Display Record Header Information
    print("Column " + str(loopCounter) + " details:")
    print("_________________________________________")
    # Display The Information Stored In The Data Record Retrieved
    print("Column name              : {}" .format(ibm_db.field_name(prepstmt, loopCounter)))
    print("Data type                : {}" .format(ibm_db.field_type(prepstmt, loopCounter)))
    print("Size                     : {}" .format(ibm_db.field_display_size(prepstmt, loopCounter)))
    print("Scale (decimal digits)   : ", end="")
    if ibm_db.field_scale(prepstmt, loopCounter) == None:
        print("Not applicable")
    else :
        print("{}" .format(ibm_db.field_scale(prepstmt, loopCounter)))
    print("Precision                : {}" .format(ibm_db.field_precision(prepstmt, loopCounter)))
    print("Display size             : ", end="")
    if ibm_db.field_display_size(prepstmt,loopCounter) == None:
        print("Not applicable")
    else:
        print("{}" .format(ibm_db.field_display_size(prepstmt,loopCounter)))
    # Increment The loopCounter Variable And Print A Blank Line To Separate The
    # Records From Each Other
    loopCounter += 1
    print()
ibm_db.close(connID)
exit(0)
Listing 8-21

Obtaining the Attributes of a Result Set

This example is somewhat similar to the Listing 8-19 program except that there are fewer attributes available. This is mostly due to the nature of a result set, which is mostly for display purposes in a Python program.

Listing 8-22 is the output from the Listing 8-21 program.
$ ./example8-15.py
Enter the Db2 userid to be used: db2inst1
Password for db2inst1:
Column 1 details:
_________________________________________
Column name              : LASTNAME
Data type                : string
Size                     : 15
Scale (decimal digits)   : 0
Precision                : 15
Display size             : 15
Column 2 details:
_________________________________________
Column name              : DEPTNAME
Data type                : string
Size                     : 36
Scale (decimal digits)   : 0
Precision                : 36
Display size             : 36
Column 3 details:
_________________________________________
Column name              : False
Data type                : False
Size                     : False
Scale (decimal digits)   : False
Precision                : False
Display size             : False
Listing 8-22

Output from Listing 8-21

The output here is similar to Listing 8-14. The addition of the DISPLAY SIZE attribute is extremely valuable as this is the number of characters needed to properly display the column data.

ibm_db_dbi and Python

The ibm_db_dbi module is actually a Python script, but it can be imported just like a Python module. The module follows the PEP 249 Python Database API Specification v2.0, and this makes your programs portable across different databases – at least that is the general idea. The specification for this module is somewhat loose, and thus it leaves a lot of room for additions, which may not be portable to other databases. This is the case for ibm_db_dbi.

Listing 8-23 is an example of a Python program that uses the ibm_db_dbi module.
#!/usr/bin/python
import sys, getpass
import ibm_db_dbi
# main program
driver = "{IBM DB2 ODBC DRIVER}"  # Db2 driver information
host = '192.168.1.201'            # database host IP or dns address
port = "50000"                    # host port
db = "sample"                     # database registered name
uid = None                        # userid on the database host
pw = None                         # password of the uid
autocommit = ''                   # autocommit default override
connecttype = ''                  # connect type default override
uid = input("Enter the Db2 userid to be used: ")
pw = getpass.getpass(prompt = "Password for %s: " % uid)
if pw == None or pw == '':
    print("The password you entered is incorrect.")
    exit(-1)
#host = host + ':' + port
conn_str = 'DRIVER=' + driver + ';HOSTNAME=' + host +
           ';PORT=' + port + ';DATABASE=' + db +
           ';UID=' + uid + ';PWD=' + pw
connID = ibm_db_dbi.connect(dsn=conn_str, conn_options=None)
# If the connection fails for any reason an uncaught exception is thrown
# and the program will exit with an error.
# get a cursor
cursor = connID.cursor()
sqlstmt = 'select * from department'
cursor.execute(sqlstmt)
# build/print header lines and fetch/print all rows
row = cursor.fetchone()
if row:
    rows = 0
    cols = len(cursor.description)
    col = 0
    typecode = []
    collen = []
    tline1 = ''
    tline2 = ''
    i = 0
    # print the report header lines
    while i < cols:
        (name,type_code,display_size,internal_size,precision,scale,null_ok) =
         cursor.description[i]
        typecode.append(type_code)
        collen.append(max(display_size, len(name)))
        tline1 = tline1 + '  ' + name +  (collen[i]-len(name))*' '
        tline2 = tline2 + '  ' + (collen[i]*'-')
        i += 1
    print(tline1 + ' ' + tline2)
    # print each fetched row
    while row:
        rows += 1
        colvals = list(row)
        i = 0
        line = ''
        while i < cols:
            (name,type_code,display_size,internal_size,precision,scale,null_ok) =
             cursor.description[i]
            if colvals[i] is None:
                line = line + '  -' + (collen[i]-1)*' '
            elif typecode[i] in ibm_db_dbi.DECIMAL:
                line = line + '  ' + (collen[i]-len(colvals[i]))*' ' + colvals[i]
            else:
                line = line + '  ' + colvals[i] + (collen[i]-len(colvals[i]))*' '
            i += 1
        print(line)
        row = cursor.fetchone()
    # print summary
    print('     ' + str(rows) + ' record(s) selected.')
connID.close()
exit(0)
Listing 8-23

An ibm_db_dbi Example Python Program

The code in this example is pretty easy to follow. What is really noticeable is the reduced amount of Python code that was needed to produce the same output as Figure 1-1. This is somewhat misleading as the ibm_db_dbi module, which is also Python code, is doing a lot of the work we did for ourselves in Listing 8-1. Thus, about the same amount of Python code is being executed when the module and our program code are taken together.

Listing 8-24 shows that you can still use parameter markers in our SQL statements.
# get a cursor
cursor = connID.cursor()
sqlstmt = 'SELECT projname, deptno FROM project WHERE deptno = ? OR deptno = ?'
cursor.execute(sqlstmt, ('B01', 'D01'))
# build/print header lines and fetch/print all rows
Listing 8-24

Using Parameter Markers with ibm_db_dbi

We are just showing the changed statements needed in Listing 8-9 that utilize parameter markers. This changes the program to match the output of Listing 8-3. Note that the parameters are passed on the execute function as a tuple.

Once again, most of the code is the same as in the previous example. This again shows that using the ibm_db_dbi module , we can leverage it to reduce the code in our Python programs. Just be aware that you should try as hard as possible to not use code that cannot be ported.

Where Is the ibm_db Module Going?

In this chapter we have covered the ibm_db module in depth, but where is it going? A careful analysis of the module source code reveals that there are some incompatibilities with the Db2 APIs. The developers acknowledge that these problems should be fixed and are in the process of figuring out what to do. They also acknowledge the documentation for the module is lacking enough examples for many Python developers.

These and other module problems will certainly be fixed in future versions, but currently they do not pose too many problems. The module is certainly usable as is as we have shown in this book. But what can be done to improve things so that Db2 has an increased user base on Windows, Linux, and Unix? Currently, the IBM developers are creating Python modules that allow programs that use a generic database API to use Db2 as the main database by creating an interface from the program’s generic interface to the ibm_db module. This will allow Db2 to store the objects needed by the program.

So far, the developers have developed four such interfaces, which we will discuss in the following.

The ibm_db_dbi Module

The ibm_db_dbi interface has been discussed in the previous section, but it is worth mentioning here. This interface is included when you install the ibm_db module. It is based on the PEP 248 specification. PEP 249 describes a generic interface that should support almost any native database query interface. The ibm_db_dbi interface fully supports this specification.

The previous section has an example of how to use the ibm_db_dbi interface. This interface calls APIs in the ibm_db interface to gain access to a Db2 database. But it is important to note that if your database changes in the future, the only change needed by your Python program is to modify the import statement so that it points to the interface file used by the new database.

By using a common generic database interface, your program becomes more portable and also much easier to maintain. The disadvantage to using a common database interface is that some of the more advanced features of a native database interface are lost to the programmer. So weigh these choices carefully when choosing your program’s interface to a database.

The Django Database Interface

The Django system is a very powerful mechanism for building web-based pages. It has many useful features and is used by a large percentage of the web programmers around the world. Django also has a generic database interface that a database supplier can use to produce a translation to the API the database supports. This is very similar to the way the PEP 248 interface works. Db2 supplies the ibm_db_django module that performs the translations necessary to access Db2 from Django.

To install the ibm_db_django module, perform the following command:
sudo pip install ibm_db
sudo pip install ibm_db_django
This will install the latest version of the ibm_db_django and the ibm_db module on your machine. All that is left to do is configure the module in the Django configuration file, and you are ready to use Db2 as your storage facility for your Django application. Listing 8-25 shows what needs to be added to the Django settings.py file.
DATABASES = {
      'default': {
         'ENGINE'     : 'ibm_db_django',
         'NAME'       : 'mydjangodb',
         'USER'       : 'db2inst1',
         'PASSWORD'   : 'xxxxxx2',
         'HOST'       : 'localhost',
         'PORT'       : '50000',
         'PCONNECT'   :  True,      #Optional property, default is false
      }
   }
Listing 8-25

Django settings.py File Extract

The following lines will also need to be added to the tuple INSTALLED_APPS in the settings.py file.
  'django.contrib.flatpages',
   'django.contrib.redirects',
   'django.contrib.comments',
   'django.contrib.admin',

The generic Django database API is fully supported by the ibm_db_django module, so no special coding is needed in your Python application. However, you may need a Db2 administrator to establish the database on the Db2 server if you do not have permission to do so.

Django by default executes without transactions, that is, in auto-commit mode. This default is generally not what you want in web applications. You should remember to turn on transaction support in Django.

To learn more about Django, you should visit the www.djangoproject.com/ website.

The SQLAlchemy Adapter

SQLAlchemy is an object-relational adapter. It transforms object information in a Python program and maps it to a relational database to allow an object to be saved from one execution of the program to the next. The transform is performed via a set of well-known patterns so that reliability of the stored object is persistent.

The SQLAlchemy toolkit enjoys wide use in the Python universe because it uses well-established rules in the transform process. These tools are well understood and have a long history of use in other object-oriented languages as well.

To install the ibm_db_sa module, perform the following command:
sudo pip install ibm_db
sudo pip install ibm_db_sa
This will install the latest version of the ibm_db_sa and ibm_db module on your machine. All that is left to do is configure the module in the CLI configuration file, and you are ready to use Db2 as your storage facility for your SQLAlchemy application. Listing 8-26 shows what needs to be added to the CLI configuration file.
[pydb]
Database=pydev
Protocol=tcpip
Hostname=host.name.com
Servicename=50000
uid=db2inst1
pwd=secret
Listing 8-26

CLI Configuration File Extract

Once the CLI configuration file has the proper settings, you are ready to start using the SQLAlchemy module in your program to create persistent copies of Python program objects to be stored in Db2.

To learn more about SQLAlchemy, you should visit the www.sqlalchemy.org/ website.

The Alembic Adapter

The Alembic project is a subproject of the SQLAlchemy project. It is a migrations tool that allows the objects stored in an SQLAlchemy relational database to be migrated to another relational database. Whatever the reason may for using a different relational database than what is currently being used by you project, Alembic will allow you to migrate all your data to a new relational database, that is, migrate an SQLite SQLAlchemy database to Db2.

While this tool is not used on a daily basis in most environments, it is available when you need it.

To install the ibm_db_alembic module, use the following command:
sudo pip install ibm_db
sudo pip install ibm_db_alembic

This will install the latest version of the ibm_db_alembic and ibm_db modules on your machine.

To learn more about Alembic, you should visit the https://alembic.sqlalchemy.org/ website.

The Future

The ibm_db module and its associated subprojects have made a really good start in allowing Python access to the Db2 environment. There are a number of possibilities for increasing this coverage, but at the time this book was written, there is one thing holding the new projects back – lack of developers. This is the same problem that prevents many open source projects from becoming all they could be. However, the ibm_db project has a slight advantage in that there is a small dedicated number of developers from IBM that are trying to move the project forward. But they need your help and support. So, if you have the time and inclination, please volunteer for the project.

Summary

In this chapter, we have introduced the ibm_db package and APIs. We have shown all the data manipulation SQL statements as well as many of the miscellaneous APIs. We have also introduced how to use parameter markers and the different ways that Python data can be bound to the statement that uses it.

Hopefully, the information and examples included in this chapter will give you a firm grasp on how you can use and manipulate a Db2 database. You also should be able to make better use of the Appendix information.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.31.50