
SQL - Procedural Programming
=============================

Overview
---------

- Queries and expressions in SQL are ideal for large set-at-a-time
  operations:

  - Operations that need to search the whole relation to answer a
    complex combination of tuples.

  - Using SQL allows database to use the query optimizer to find the
    best query execution plan for the query.

- However, SQL is not efficient for some programming tasks:

  - Sequence of code: A block of queries that must be executed in an order
  - Conditionals: if/then/else statements (some simple ones can be
    handled, but cannot be combined with sequence of code
  - Loops 
  - Store values in and use values from program variables
  - Error handling


Example Program
---------------

- We will see an example program that is hard to write using an SQL
  query, but trivial with a procedural language.

- Find the top 3 most frequent y for each x in table R(x,y,z).

  - Not very easy!

  - However, suppose we have a query:

    ::
       
       SELECT x,y, count(*) num FROM R GROUP BY x,y ORDER BY x,y, num;
       
    Now, we can write a simple program to compute this as follows:
  
    ::

       Algorithm Top 3 y for each x:
          Run query:
	       SELECT x,y, count(*) num
	       FROM R
	       GROUP BY x,y
	       ORDER BY x,y, num;
	  In a loop read each tuple such that:
	      if a new x is found:
	          read the next three y values
		  (error handling if less than three y values are found)
		  skip remaining x values

Procedural SQL
----------------

- To enable the use of SQL for costly queries, while making it
  possible to write code/procedures on top of it, databases support a
  number of options.

- The options belong in two main options:  

  - Server side
  - Client side

Server side
~~~~~~~~~~~~~

- Languages make it possible to define procedures, functions and
  triggers
- These programs are compiled and stored within the database server
- They can also be called by queries

Client side
~~~~~~~~~~~~

- Languages allow programs to integrate querying of the database with
  a procedural language
- Coding in a host language with db hooks (C, C++, Java, Python, etc.)
  using the data structures of these languages
- Coding in frameworks with their own data models (Java, Python, etc)
  with similar db hooks as in above.

Programming with SQL
-----------------------

- All programming paradigms support:

  - Methods to execute queries and update statements

    Executing any SQL statement and catching the outcome 

  - Interpret errors returned if any 

  - Input values from variables into queries, output query results
    into variables
  - Loop over query results (for queries returning multiple tuples)
  - Raise exceptions (which results in rollback of transactions)
  - Store and reuse queries in the shape of cursors
  - Starting and committing transactions

- Client side programs also support:
  
  - Opening/closing connections, allocating/releasing database
    resources for queries

- The actual syntax of the methods for doing this vary, but the
  principle is the same.

Server side language examples
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- Generally database specific

- Postgresql:

  - pl/pgsql: generic procedural language for postgresql
  - pl/pyhton: procedural language that is an extension of python
    (also see pl/tcl, pl/perl)
  
Client side language examples
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- Database specific extensions of host languages, for example

  - libpq: C library for postgresql, uses library calls for specific
    to postgresql
  - OCCI: Oracle library for C

  Write code in C with hooks to the database in the form of
  functions and libraries, and compile using C compiler
  
  - ECPG: embedded programming in SQL, based on the embedded
    programming standard with a postgresql specific pre-compiler, an
    the standard C compiler

- Standard libraries for connecting to the database

  - JDBC is a Java standard library, all databases implement the same
    functions (some non-standard data types will be different)

    To compile code with JDBC functions, the specific adapter
    (library) for a database must be used.

  - No specific standard for libraries based on C, but ODBC is a
    non-language specific standard, .NET tries to achieve the same

  - Python DB-API is a database independent framework for Python
    
    Similar to Java, a module for each database management system is
    needed. The postgresql module is called psycopg2.

Frameworks
-----------

- Frameworks are based on specific design principles for developing
  database backed applications
- Examples:

  - Object-relational-mapping used by Rails, Hibernate, Django,
    WebObjects, .NET (different frameworks have different models)

  - Note that the frameworks can be built on top of other languages
    (such as Java + JDBC)

pl/pgsql
----------

- Note that languages may be different, but the way that communicate
  with SQL is very similar.
- We will go into detail of one language and then show similar
  concepts in other paradigms.

  See full documentation here:

  http://www.postgresql.org/docs/11/plpgsql.html

- pl/pgsql supports the same data types as the database

- Programs and functions can be compiled and used directly at the db
  server

- Main pl/pgsql block has the form:

  ::
     
     [ <<label>> ]
     [DECLARE
     variable declarations ]
     BEGIN
     statement
     END [ label ] ;

-  Variable types:

   ::

      integer
      numeric(5)
      varchar
      tablename%ROWTYPE
      tablename.columname%TYPE
      RECORD

   ROWTYPE and RECORD have subfields, i.e. x.name.

Programming constructs
~~~~~~~~~~~~~~~~~~~~~~~

- Conditionals:

  ::

     IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF 
     Loops:
     [ <<label>> ]
     LOOP
     statements
     END LOOP [ label ];
     
- Returning a value:

  - pl/pgsql functions do not allow you to modify input variables
    
  - RETURN will return a value. As a result, you can call it like a
    constant in the select statement shown below:

  ::

     
     CREATE FUNCTION sales_tax(subtotal real, state varchar) RETURNS real AS $$
     DECLARE
        adjusted_subtotal real ;
     BEGIN
        IF state = 'NY' THEN
            adjusted_subtotal = subtotal * 0.08 ;
        ELSIF state = 'AL' THEN
            adjusted_subtotal = subtotal ;
        ELSE
            adjusted_subtotal = subtotal * 0.06;
        END IF ;
        RETURN adjusted_subtotal ;
     END ;
     $$ LANGUAGE plpgsql ;

- Now, test it:

  ::

     select sales_tax(100, 'NY') ;
     
     sales_tax 
     -----------
     8
     (1 row)

- Note: The whole body of the function is entered within the two $$ signs.


- You can also return a table of rows:
    
    - Return each tuple with RETURN NEXT and finish with RETURN
    - As these return a table, they are called in the FROM clause. See the
      loop section below for examples.

- Starting with Version 11, Postgresql also allows procedures that modify
  input variables. You can read about them in the documentation.

Handling SQL:
~~~~~~~~~~~~~~~~

- We will consider three types of SQL expressions:

  #. Statements that return no output but a status (successful or not,
     and what was wrong)
  #. Statements that return a single tuple
  #. Statements that return multiple tuples 

  ::
     
     CREATE FUNCTION sales_tax(subtotal real) RETURNS boolean AS $$
     DECLARE
        adjusted_subtotal real ;
     BEGIN
        adjusted_subtotal = subtotal * 0.06;
        BEGIN
            INSERT INTO temp VALUES (adjusted_subtotal) ;
            RETURN true ;
        EXCEPTION WHEN unique_violation THEN
            RETURN false ;
        END ;
     END ;
     $$ LANGUAGE plpgsql ;

- Now, when you run this function, a row is inserted into table temp.

Executing queries
~~~~~~~~~~~~~~~~~~

- When the query returns a single row, then we can read it directly
  into a variable.
  
- Note that when using variables as input/output, pl/pgsql does not
  need any special delimiters (be careful naming the variables so as
  not to clash with column names)
   
- Example:
  ::
     
     SELECT * INTO myrec FROM emp WHERE empname = myname;
     IF NOT FOUND THEN
     RAISE EXCEPTION 'employee % not found', myname;
     END IF;

   - input: myname, output: myrec

Executing queries
~~~~~~~~~~~~~~~~~~~~~
- When the query returns multiple rows, then a loop is needed to go
  through them one by one.

  - A query returns a stream of tuples, which needs to be processed.

  - Equally important is closing the stream associated with a query
    if required by the programming language.

- Example:

  ::

     [ <<label>> ]
     FOR target IN query LOOP
     statements
     END LOOP [ label ];

     DECLARE
     myRow  RECORD ;
     lastX      INT ;
     yCnt       INT ;
     BEGIN
        lastX = 0 ;
        yCnt = 0 ;
        FOR myRow IN
               SELECT x,y, count(*) as num
               FROM temp GROUP BY x,y ORDER BY x, num ASC LOOP
           yCnt = yCnt + 1;
           IF yCnt < 4 AND lastX = myRow.x THEN
               INSERT INTO temp2 VALUES(myRow.x, myRow.y, myRow.num) ;
           ELSIF lastX <> myRow.x THEN
               lastX = myRow.x ;
               yCnt = 1 ;
               INSERT INTO temp2 VALUES(myRow.x, myRow.y, myRow.num) ;
           END IF ;
        END LOOP ;
     RETURN 1 ;
     END ;

- Note that procedure proc2() computes and inserts the top 3 y values
  (by count) for each x.  Call it as:

  ::

     SELECT proc2() ;

- Here is an example of function that returns a table. Note that the
  table structure has to be defined in advance.

  ::

     CREATE TABLE names (name VARCHAR(255)) ;
     
     CREATE FUNCTION allnames() RETURNS SETOF names AS $$
     DECLARE
        row RECORD ;
     BEGIN
        FOR row in SELECT DISTINCT crsname FROM courses LOOP
	    RETURN NEXT row ;
	END LOOP ;
        RETURN ;
     END ;
     $$ LANGUAGE plpgsql ;

- Since this function returns a table, we call it in the FROM clause:

  ::
     
     university=> select * from allnames();
               name          
     ------------------------
     Spellcasting
     Practical Applications
     Mazes
     Alternate Dimensions
     Minor Mendings
     Transmogrification
     Advanced Spellcasting
     (7 rows)

-  You can also use the tables returned by functions in queries:

   ::
       university=> select * from allnames() a, courses c where a.name=c.crsname;


     
Cursors
~~~~~~~~

- A cursor is a query with a handle. Cursors may have input.
- Cursors may be defined once, and used many times to read tuples.

  A query in a cursor is optimized once, reducing the cost of
  optimizing the query many times.
  
- Functions may return reference to a cursor, allowing a program to
  read tuples that are returned.
- Cursors provide a more efficient implementation of queries returning
  many tuples.

- First, declare cursors:

  ::

     DECLARE curs2 CURSOR FOR SELECT * FROM tenk1;

- Then, execute the associated query by opening them:

  ::

     OPEN curs2;

- Then, retrieve tuples in the result using fetch:

  ::
     
     FETCH curs2 INTO foo, bar, baz;

or

  ::
     
     FOR recordvar IN curs2 LOOP

-  When finished, close the cursor to release allocated memory:

   ::

      CLOSE curs2;
  
- Cursors can also be used for update/delete if it is pointing to a
  specific tuple (similar to the notion of an updatable view).
  - Update/delete the tuple the cursor is pointing to.

Exceptions
~~~~~~~~~~~~

- When an SQL statement is executed, if it is not successful, it raises an error. This error can be caught in the usual try/catch format:

  ::
     
     BEGIN
     statement
     EXCEPTION WHEN condition THEN
       statement
     END ;

- The exception conditions define integrity violations, statement
  errors, connection errors, etc.

- The pl/pgsql statements can also raise exceptions to be caught by
  the calling statement:

  ::
     
     RAISE NOTICE  ''
     RAISE EXCEPTION ''

- Also uncaught exceptions within the function will be raised when the
  function fails.

  
Other languages and formalisms for procedural programming
----------------------------------------------------------

- Given our introduction to programming with pl/pgsql, we will now
  cover some alternate languages to program in databases.

- Each will have constructs similar to pl/pgsql, some will be generic
  and some will be database specific. I invite you to compare them to
  each other and notice similarities.

  
Client-side programming
-----------------------

- Client-side programming languages have additional constructs to
  connect to a database, keep a pool of open connections and close
  connections.

Running and Debugging
~~~~~~~~~~~~~~~~~~~~~~

- Depending on how tight the integration with the database, error
  checking becomes an issue.

- Two sources of syntax errors:

  - Host language syntax errors

  - SQL syntax errors: note that often these are not checked until you
    send the query to the server. So, it is difficult to debug.

- Some client-side programming paradigms are a hybrid of host language
  and additional constructs.

  - Example: Embedded SQL is an SQL standard for writing programs in a
    host language.

  - There is a precompilation phase for only the SQL code and
    variables with better error checking.

  - SQL calls are changed to calls in the host language after
    precompilation.

  - Though writing programs in this paradigm can be a bit awkward and
    it is a less popular platform as a result.

    
Performance
~~~~~~~~~~~~

- One must be especially careful about sending large data sets over
  the network when writing client code.

- When writing client-side code, you must balance the work that must
  be done in the server side and client side.

  - Very complex queries slow down performance.
  - Very simple queries lead to very large data sets being sent over
    the network.

Call level interfaces for client-side programming
--------------------------------------------------

- Supplies the constructs for opening connections, running queries,
  looping over them etc.
- SQL is completely handled with special function calls in the host
  language
- JDBC is an industry standard for Java, supported by all databases
  using drivers.
- Other database specific examples: OCCI for C in Oracle, Libpq for C
  in Postgresql

OCCI Example
--------------

- OCCI is a C-library specific to Oracle, but it is designed to
  very closely resemble JDBC for Java which is a standard.

  ::
     
     #include <occi.h>
     using namespace oracle::occi;

     Environment* const env = Environment::createEnvironment(Environment::DEFAULT);

     Connection* const con = env->createConnection(user, pass, osid);

     Statement* const s = 
            con->createStatement("SELECT a.stageName"
                                 " FROM movies.actors a"
                                 " WHERE a.stagename like 'A%'");

Initialization
~~~~~~~~~~~~~~~~~~~~

- Each OCCI program must initialize an environment at the start of a
  program.

  ::
     
     static Environment* Environment::createEnvironment (Environment::DEFAULT)
     
- The Environment object contains the memory allocator and
  thread-library configuration for OCI.
- You should explicitly terminate an environment at the end of a
  program to release all memory.

  ::

     Environment::terminateEnvironment(env);
     
Connection
~~~~~~~~~~~~~~

- After initialization, you must open a connection to the specific database instance you are going to query. 

  ::

     Connection* Environment::createConnection (string username, string password, string sid)
     
- It will authenticate the given user and password for the given instance id.

  ::

     Connection* con = env->createConnection("scott",  "tiger", "ora9i");
     
- A single connection can be used to query the same database multiple
  times in parallel or sequentially.
  
- You should terminate your connection at the end of your program to
  release all memory at the client and the database server.

  ::

     env->terminateConnection(con);

     
Querying
~~~~~~~~~~

- Once you have established a connection to the database, you are
  ready to execute queries and updates.

- To execute a query, you will need to:

  - Create an SQL statement and load it into a ``statement`` type object.
  - Execute your query which will return one or more tuples. 
  - Create a ``resultset`` object that will allow you to iterate through
    the tuples returned by the query.
  - Close your resultset object so that the database and your program
    releases the necessary memory.
  - Close your statement if you will no longer use it. Note that you
    can use a single statement object repeatedly with different SQL
    queries.

Statements
~~~~~~~~~~~~~~

- Create a statement for a specific connection:

  ::

     Statement* sel_all_stmt
     con->createStatement("SELECT attr1 FROM my_table");

     ..statements to execute this query here…

- Change the query for this statement if necessary:

  ::

     sel_all_stmt->setSQL("SELECT attr2, attr3 FROM new_table");

- When finished, release the statement object.

  ::

     con->terminateStatement(sel_all_stmt);

     
Parametrized Statements
~~~~~~~~~~~~~~~~~~~~~~~~~

- Very often statements are executed multiple times with different
  values.
- For example, suppose a query that finds the name of a specific
  employee may be executed multiple times for different employees.

  ::

     Statement* sel_name con->createStatement("SELECT name FROM employee WHERE id = :1");
     
- This means this query will need to be supplied by one value before it is executed.

  ::

     sel_name->setInt(1, 112223333);
     
- The type used in the “set” method should set the type of the value
  being supplied.
- This type of a query is UNPREPARED if the required value is not
  supplied by the program.
- A prepared statement is optimized once and the query plan is used
  multiple times for each execution of the query saving execution
  time.
  

  ::

     Statement* sel_name con->createStatement("SELECT name FROM employee WHERE id = :1 AND Office = :2");
     sel_name->setInt(1, 112223333);
     sel_name->setString(2, “AE125”);

- Or

   ::

      Statement* sel_name con->createStatement("SELECT name FROM employee WHERE id = :1 AND Office = :2");
      sel_name->setInt(1, ssnVar);
      sel_name->setString(2, officeVar);

- Where ssnVar and officeVar are program variables of types integer
  and string respectively containing the necessary values.
  
Update statements
~~~~~~~~~~~~~~~~~~~~

- All statements that change the database are executed using
  executeUpdate method.
- Examples are insert, update, delete, create …, drop … statements

  ::

     stmt->executeUpdate(“CREATE TABLE basket_tab (fruit VARCHAR2(30), quantity NUMBER)”); 

     statement* s1 = 
     con->createStatement("INSERT INTO my_table (a, b) VALUES (1, 'A')");
     s1->executeUpdate();
     
- Update statements return the total number of tuples effected which
  can be returned by getUpdateCount() method.
  
Select statements
~~~~~~~~~~~~~~~~~~~

- A SELECT query returns one or more tuples by the execution of the
  executeQuery method.
- To process these tuples, you need a result set object which
  processes tuples in a similar way to a file.
- You need to open, iterate through and close a result set to access the tuples.

  ::

     statement* s1 = 
     con->createStatement("SELECT name FROM emp WHERE id < 1000");
     ResultSet r = s1->executeQuery();
     
- Query is executed, the tuples are returned to the program and the
  result set is initialized to the before the first item in the
  results.
- To find the first item, you need to execute "next" operation.
  
- next returns false if the last tuple is already been read.
  
  ::

     statement* s1 = con->createStatement(
              "SELECT id, name FROM emp WHERE id < 1000");
     ResultSet r = s1->executeQuery();
     while (r->next()) {
         varId = r->getInt(1) ;
         varName = r->getString(2) ;
     }
     s->closeResultSet(r);
     
- To read the columns of the current tuple pointed to by the result
  set, get methods are used.
- The type of these methods much match the type of the column returned
  by the program.
  - getXXX(i)  means attribute i of the query should have type XXX.
    
Errors and Status
~~~~~~~~~~~~~~~~~~
- An SQLException is raised for many things that may go wrong for your program:
  - Connection cannot be established
  - The query cannot be executed, etc.
- The exceptions can be caught and checked in the program.

  ::
     
     try{  
        ... operations which throw SQLException ...
     }
     catch (SQLException e){  
        cerr << e.what();
     }

- Other useful methods: ``e.getMessage()``, ``e.getErrorCode()``
  
Errors and Status
~~~~~~~~~~~~~~~~~~~
- As statements are created dynamically and executed in the program,
  it may be necessary to check their status while the program is
  running.
  
- This is accomplished with the status methods for each class.
  
- Examples:
  
  - Check whether the statement is ``UNPREPARED, PREPARED,
    RESULT_SET_AVAILABLE, or UPDATE_COUNT_AVAILABLE``.
    
  - Check whether the result set is ``END_OF_FETCH = 0``,
    ``DATA_AVAILABLE``.
    
- Other methods exist to check for other status related
  information. For example, check whether the result set is empty
  (``r->isNull``).
  
Dynamic Conditions
~~~~~~~~~~~~~~~~~~~

- Suppose tableName is a program variable set to a specific constant by the user.

  ::

     sqlString = "SELECT * FROM " << tableName ;
     statement* s1 = con->createStatement(sqlString);
     ResultSet r = s1->executeQuery();
     while (r->next()) {
		// what attributes are in this result set?
     }
     s->closeResultSet(r);

- The function:
  
  ::

     vector<MetaData> getColumnListMetaData() const; 

  that returns number, types and properties of aResultSet’s columns.
  
Transactions
~~~~~~~~~~~~~
- A transaction starts with the first executeQuery, executeUpdate
  statement and ends until the first commit, abort and rollback
  statement executed for the given connection.

  ::

     con->commit();
     con->rollback();
     
- After a rollback/commit, the next query/update will start a new transaction. 


JDBC
-------

- OCCI is an Oracle specific language for enabling communications
  between a C++ program and a database.
  
- JDBC is a standard for any database product and a Java program for
  the same purpose.
  
- JDBC and OCCI are very similar to each other and have almost
  identical set of classes and methods. In fact, OCCI is based on
  JDBC.
  
- To accomplish the communication between a Java program and a
  database, a set of libraries called a "driver" is needed.
  
- JDBC drivers are specific to the database server.

- Example program:

  ::

     import java.sql.*;
     import oracle.sql.*;
     import oracle.jdbc.driver.*;
     class Employee
     {
     public static void main (String args []) throws SQLException
     {//Set your user name and the password
     String userName = "dummy" ;
     String passWord = "dummy" ;

     // Load the Oracle JDBC driver
     DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

	Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@acadoracle.server.rpi.edu:1521:ora9",userName,passWord);
     // Create a statement which will return a cursor that 
     // will allow you to scroll the result set using both 
     // "next" and "previous" methods
     
     try {  
           Statement stmt = conn.createStatement
		(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
		
	   ResultSet rset = stmt.executeQuery("SELECT name, oid FROM items ");

	   // Iterate through the result and print the item names
	   while (rset.next ()) {
   	       //Get item name, which is the first column
	       System.out.println (rset.getString (1));
	       
	       PreparedStatement pstmt = conn.prepareStatement ("SELECT name FROM owners WHERE oid = ?") ;
			  
	       //Feed the owner id retrieved from rset into pstmt
	       pstmt.setInt(1, rset.getInt (2));
	       ResultSet dset = pstmt.executeQuery() ;
               if (dset.next())
                   System.out.println(dset.getString (1));
           } }
      }
      catch (SQLException) { error-handling-code } }  }

Python DB-API
----------------

- DB-API is a generic db interface for python (like JDBC). 
- psycopg2 is a python adapter that implements DB-API.

  ::

     import psycopg2 as dbapi2

     db = dbapi2.connect (database="python", user="python", password="python")
     cur = db.cursor()
     cur.execute ("INSERT INTO versions VALUES ('2007-10-18', '2.4.4', 'stable')")
     conn.commit ()
     cur.close()
     db.close()

- Example 2:     

  ::
     
     import psycopg2 as dbapi2

     db = dbapi2.connect (database="python", user="python", password="python")
     cur = db.cursor()

     cur.execute ("SELECT * FROM versions");
     rows = cur.fetchall()
     for i, row in enumerate(rows):
         print "Row", i, "value = ", row

     try:
         cur.execute ("""UPDATE versions SET status='stable' where version='2.6.0' """)
	 cur.execute ("""UPDATE versions SET status='old' where version='2.4.4' """)
	 db.commit()
     except Exception, e:
         db.rollback()
      

libpq: Postgresql C-language interface
---------------------------------------

- The C-language interface for Postgresql uses a number of
  function calls to commmunicate with the database.

- Example:

  ::

     #include <stdio.h>
     #include <stdlib.h>
     #include "libpq-fe.h”
     static void
     exit_nicely(PGconn *conn)
     {
         PQfinish(conn);
         exit(1);
     }

     int
     main(int argc, char **argv)
     {
         const char *conninfo;
	 PGconn     *conn;  PGresult   *res;
	 int         nFields;
	 int         i,  j;
	 
	 conninfo = "port=5432 dbname='sibel' host='localhost' user='sibel' ";
	 conn = PQconnectdb(conninfo);
	 if (PQstatus(conn) != CONNECTION_OK)    {
    	     fprintf(stderr, "Connection to database failed: %s",
             PQerrorMessage(conn));
	     exit_nicely(conn);
	 }

	 /* Start a transaction block */
	 res = PQexec(conn, "BEGIN");

	 if (PQresultStatus(res) != PGRES_COMMAND_OK)
	 {
   	      fprintf(stderr, "BEGIN command failed: %s", PQerrorMessage(conn));
	      PQclear(res);
	      exit_nicely(conn);
	 }
	 /* Should PQclear PGresult whenever it is no longer needed to avoid\
	 memory leaks */
	 PQclear(res);
 
	 res = PQexec(conn, "DECLARE myportal CURSOR FOR select * from pg_database");
	 if (PQresultStatus(res) != PGRES_COMMAND_OK)
	 {
	     fprintf(stderr, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
	     PQclear(res);
	     exit_nicely(conn);
	 }

	 res = PQexec(conn, "FETCH ALL in myportal");
	 if (PQresultStatus(res) != PGRES_TUPLES_OK)
	 {
	     fprintf(stderr, "FETCH ALL failed: %s", PQerrorMessage(conn));
	     PQclear(res);
	     exit_nicely(conn);
	 }
	 /* first, print out the attribute names */
	 nFields = PQnfields(res);
	 for (i = 0; i < nFields; i++)
  	     printf("%-15s", PQfname(res, i));
	 printf("\n\n");
	 /* next, print out the rows */
	 for (i = 0; i < PQntuples(res); i++)
	 {
  	     for (j = 0; j < nFields; j++)
                 printf("%-15s", PQgetvalue(res, i, j));
	     printf("\n");
         } 

	 PQclear(res);

	 /* close the portal ... we don't bother to check for errors ... */
	 res = PQexec(conn, "CLOSE myportal");
	 PQclear(res);

	 /* end the transaction */
	 res = PQexec(conn, "END");
	 PQclear(res);

	 /* close the connection to the database and cleanup */
	 PQfinish(conn);

	 return 0;
     }
