Table of Contents

KInterbasDB Usage Guide

Contents


Python Database API 2.0 Compliance

Incompatibilities

All known outright incompatibilities between KInterbasDB and the Python Database API Specification 2.0 have been fixed in KInterbasDB 3.0. However, some optional features are not implemented (or are only nominally implemented) due to limitations in the Interbase/Firebird database engine (see below).

Unsupported Optional Features

Nominally Supported Optional Features

Extensions and Caveats


Unsupported Database Engine Features

KInterbasDB does not support the following features of the Interbase/Firebird database engine:


Tutorial

This is not a comprehensive Python Database API tutorial, nor is it comprehensive in its coverage of anything else. It merely aims to demonstrate common KInterbasDB usage patterns, and to illustrate useful features of KInterbasDB that the Python Database API specification does not address.

Connecting to a Database

Example 1

A database connection is typically established with code such as this:

# The server is named 'stalin'; the database file is at
# 'd:/code/projects/kinterbasdb/ibtest.db'.

import kinterbasdb

con = kinterbasdb.connect(
        dsn="stalin:d:/code/projects/kinterbasdb/ibtest.db",
        user="sysdba",
        password="pass"
    )

Example 2

Suppose we want to connect to an Interbase 5.5 server, specifying UNICODE_FSS as the character set of the connection:

import kinterbasdb

con = kinterbasdb.connect(
        dsn="stalin:d:/code/projects/kinterbasdb/ibtest.db",
        user="sysdba",
        password="pass",
        dialect=1, # necessary for Interbase < 6.0
        charset="UNICODE_FSS" # specify a character set for the connection
    )


Executing SQL Statements

For this section, suppose we have a table defined and populated by the following SQL code:

create table people
(
  name_last      varchar(20),
  age            integer
);

insert into people (name_last, age) values ('Yeltsin', 69);
insert into people (name_last, age) values ('Gorbachev', 72);

Example 1

This example shows the simplest way to print the entire contents of the people table:

import kinterbasdb

con = kinterbasdb.connect(
        dsn="stalin:d:/code/projects/kinterbasdb/ibtest.db",
        user="sysdba",
        password="pass"
    )

cur = con.cursor() # Get a Cursor object that operates in the context of
                   # Connection con.

cur.execute("select * from people") # Execute the SELECT statement.

print cur.fetchall() # Retrieve all rows as a sequence, then print that sequence.

Sample output:

[('Yeltsin', 69), ('Gorbachev', 72)]

Example 2

Let's try a more meaningful example. Suppose we want to print the contents of the name_last field for every row in the people table:

import kinterbasdb

con = kinterbasdb.connect(
        dsn="stalin:d:/code/projects/kinterbasdb/ibtest.db",
        user="sysdba",
        password="pass"
    )

cur = con.cursor() # Get a Cursor object that operates in the context of
                   # Connection con.

cur.execute("select name_last from people") # Execute the SELECT statement.

print 'NAME_LAST'
print '---------'

while 1: # Loop until instructed otherwise.
    row = cur.fetchonemap() # Get a mapping of field name to field value
                            # for the next available row.
    if row is None: # If we've reached the end of the record set, end the loop.
        break

    print row['name_last'] # Print the value of the name_last field in the
                           # current row.

Sample output:

NAME_LAST
---------
Yeltsin
Gorbachev

Example 3

The following program is a generic table printer (applied in this example to people):

import kinterbasdb, string

TABLE_NAME = 'people'

con = kinterbasdb.connect(
        dsn="stalin:d:/code/projects/kinterbasdb/ibtest.db",
        user="sysdba",
        password="pass"
    )

cur = con.cursor()

cur.execute("select * from %s" % TABLE_NAME)

# Print a header.
for d in cur.description:
    print (
        string.ljust(
            d[kinterbasdb.DESCRIPTION_NAME],
            d[kinterbasdb.DESCRIPTION_DISPLAY_SIZE]
        )
    ),
print # Finish the header row with a newline.
print '-' * 78

# Print each row.
while 1:
    row = cur.fetchone()
    if row is None:
        break

    # Print the value of each field left-justified within the maximum 
    # possible width of that field. 
    for fieldPos in range(len(row)):
        print (
            string.ljust(
                str(row[fieldPos]),
                cur.description[fieldPos][kinterbasdb.DESCRIPTION_DISPLAY_SIZE]
            )
        ),
    print # Finish the row with a newline.

Sample output:

NAME_LAST            AGE
------------------------------------------------------------------------------
Yeltsin              69
Gorbachev            72

Example 4

Let's insert more people into the people table:

import kinterbasdb, string

con = kinterbasdb.connect(
        dsn="stalin:d:/code/projects/kinterbasdb/ibtest.db",
        user="sysdba",
        password="pass"
    )

cur = con.cursor()

newPeople = (
        ('Lebed'       , 56),
        ('Zhirinovsky' , 49)
    )

for newPerson in newPeople:
    cur.execute("insert into people (name_last, age) values (?, ?)", newPerson)

con.commit() # The changes will not be saved unless the transaction is 
             # committed explicitly.

Note the use of a parameterized SQL statement above. When dealing with repetitive statements, this is faster and less error-prone than assembling each SQL statement manually.

After running Example 4, the table printer from Example 3 would print:

NAME_LAST            AGE
------------------------------------------------------------------------------
Yeltsin              69
Gorbachev            72
Lebed                56
Zhirinovsky          49

Calling Stored Procedures

Interbase and Firebird support stored procedures written in a propriety procedural SQL language. IB/FB stored procedures can have input parameters and/or output parameters, but not combined input/output parameters.

It is important to distinguish between procedures that return a result set and procedures that populate and return their output parameters exactly once (conceptually, these latter "return their output parameters"). IB/FB's server-side procedural SQL syntax makes no such distinction, but client programmers must do so in their SQL code. A result set is retrieved from a stored procedure by SELECTing from the procedure, whereas output parameters are retrieved with an EXECUTE PROCEDURE statement.

To retrieve a result set from a stored procedure with KInterbasDB, use code such as this:

cur.execute("select output1, output2 from the_proc(?, ?)", (input1, input2))

# Ordinary fetch code here, such as:
result_set = cur.fetchall()

To call a stored procedure and access its output parameters with KInterbasDB, use code such as this:

cur.callproc("the_proc", (input1, input2))

# If there are output parameters, retrieve them as though they were the
# first row of a result set.  For example:
output_params = cur.fetchone()

Controlling Transactions

The Interbase engine requires that virtually every database operation take place in the context of a transaction. Even a typical SELECT statement has an associated transaction.

For the sake of simplicity, KInterbasDB lets the Python programmer ignore transaction management to the greatest extent allowed by the Python Database API Specification 2.0. The specification says, "if the database supports an auto-commit feature, this must be initially off". At a minimum, therefore, it is necessary to call the commit method of the connection in order to persist any changes made to the database. Transactions left uncommitted by the programmer will be rollbacked when the connection is garbage collected.

KInterbasDB also supports explicit transaction management. Connections have a default_tpb (default transaction parameter buffer) attribute that can be used to specify the characteristics of all transactions subsequently started on the connection. The programmer can also start a transaction explicitly using the connection's begin method, which may optionally be passed a transaction parameter buffer for that single transaction. If begin indeed receives a transaction parameter buffer, it will be used for that particular transaction, in place of the connection's default_tpb.

For more information, see the documentation of Connection.default_tpb elsewhere in this document or Chapter 5 of the Interbase 6 API Guide.

The following example establishes an unobtrusive transaction to be used for read-only access to the database:

import kinterbasdb

con = kinterbasdb.connect(
    'stalin:d:/code/projects/kinterbasdb/ibtest.db',
    'sysdba', 'pass')

tpb = kinterbasdb.isc_tpb_read \
    + kinterbasdb.isc_tpb_read_committed \
    + kinterbasdb.isc_tpb_rec_version

con.begin(tpb)

# Now read some data using a cursor...

Using the database_info Function

The kinterbasdb.database_info function is a very thin wrapper around the Interbase/Firebird C API function isc_database_info (documented in the Interbase 6 API Guide section entitled "Requesting information about an attachment" that begins on page 51).

Reference documentation for kinterbasdb.database_info is already provided in the Extensions and Caveats section of this document, so this tutorial only supplies an example program:

import kinterbasdb

con = kinterbasdb.connect(
    'stalin:d:/code/projects/kinterbasdb/ibtest.db',
    'sysdba', 'pass')

# Retrieving an integer info item is quite simple.
bytesInUse = con.database_info(kinterbasdb.isc_info_current_memory, 'i')

print 'The server is currently using %d bytes of memory.' % bytesInUse

# Retrieving a string info item is somewhat more involved, because
# the information is returned in a raw binary buffer that must
# be parsed according to the rules defined in the Interbase 6 API
# Guide section entitled "Requesting buffer items and result buffer
# values" (page 51).
# Often, the buffer contains a succession of length-string pairs
# (one byte telling the length of s, followed by s itself).
# Function kinterbasdb.raw_byte_to_int is provided to convert a raw
# byte to a Python integer (see examples below).
buf = con.database_info(kinterbasdb.isc_info_db_id, 's')

# Parse the filename from the buffer.
beginningOfFilename = 2
# The second byte in the buffer contains the size of the database filename
# in bytes.
lengthOfFilename = kinterbasdb.raw_byte_to_int(buf[1])
filename = buf[beginningOfFilename:beginningOfFilename + lengthOfFilename]

# Parse the host name from the buffer.
beginningOfHostName = (beginningOfFilename + lengthOfFilename) + 1
# The first byte after the end of the database filename contains the size
# of the host name in bytes.
lengthOfHostName = kinterbasdb.raw_byte_to_int(buf[beginningOfHostName - 1])
host = buf[beginningOfHostName:beginningOfHostName + lengthOfHostName]

print ('We are connected to the database at %s on host %s.' % (filename, host))

Sample output:

The server is currently using 17614848 bytes of memory.
We are connected to the database at D:\CODE\PROJECTS\KINTER~1\IBTEST.DB on host STALIN.



Table of Contents