UnixWorld Online: Tutorial Article: No. 018

Linux, SQL, and the Web

By Charles Fisher

Historically, the construction of SQL-enabled Web applications has not been easy. Such systems usually required not only expensive database software, but also programmers familiar with C, the CGI specification, and dialects of embedded SQL. PHP 3.0 and PostgreSQL stand to change this state of affairs. PHP allows SQL scripting to be embedded in HTML files, and PostgreSQL brings a reasonably powerful database to the masses. This article presents a complete Linux-based, SQL-enabled Web application, hiding none of the details.



Questions regarding this article should be directed to the author at charles_fisher@bigfoot.com

How You Can Use This Information

Hypertext Markup Language was a tremendous innovation, but its principle weakness is its static nature. HTML was intended to be a method for sharing documents, not an interactive medium.

However, if your Information Systems needs require general global visibility, the Web is an obvious choice. The Web has achieved such ubiquity that efforts to overcome its limitations are a primary developer concern. Technologies -- such as Java, ActiveX, and XML -- represent the recent attempts to extend the power of the Web.

PostgreSQL is a successor to the Postgres DataBase Management System (DBMS). A DBMS enables the fast storage and retrieval of large amounts of information. The interface that is used to insert, manipulate, and extract data is called the Structured Query Language (SQL), which was developed by IBM for their DB2 database product in the 1970s. PostgreSQL implements a subset of ANSI-standard SQL (most notably, the version discussed here does not implement sub-queries or outer joins). PostgreSQL runs on a variety of Unix platforms. Precompiled binaries for PostgreSQL are now shipped standard in Red Hat Linux 5.0.

Without an interface to link it to the Web, however, a SQL database will be of little use. Middleware software is required to meld HTML and SQL into a single format.

PHP (Professional Home Pages), the successor to PHP/FI, is a utility that has enjoyed a great deal of popularity. It allows a C-like programming language with SQL extensions to be directly embedded within HTML documents. The software has recently been through a complete rewrite with a development emphasis on performance. PHP requires no extensions to the browser as it relies upon the CGI interface.

Please note that PHP and PostgreSQL do not provide a total electronic commerce solution. The Apache Web Server , included with Linux, does not provide SSL encryption. Some sources for SSL extensions for Apache are the commercial Stronghold version of Apache with SSL support and SSLeay . Web sites might also need credit-card validation software. CyberCash is one such vendor.

The information presented within this document is intended for Intel-based systems running Red Hat Linux 5.0. Much of the same information will be applicable, however, to other platforms as all of the software discussed here (PostgreSQL, PHP 3.0) has been ported to a wide variety of Unix implementations.

Major Challenges

If you are running Red Hat Linux 5.0, the task before you is easiest. An older version of the PostgreSQL database is included in the binary distribution. It can easily be loaded when the system is installed by selecting it from the component menu. It is also possible to install PostgreSQL on a running system with RPM commands.

If you are running an earlier release of Red Hat Linux (4.2 or below), you can install an RPM containing PostgreSQL. However, if you are running another Unix variant, you must download and compile PostgreSQL.

PHP is not included in Red Hat Linux 5.0. It must be downloaded and installed regardless of the Unix version that is being used. There are a number of performance options that can be selected at compile time, which will be discussed in a later step.

Installing PostgreSQL

PostgreSQL has a Web site with links to the latest versions of the database and a complete library of documentation. The Web site is in need of funding. That is, although PostgreSQL is a free object-oriented RDBMS, the developers are asking for a small donation to be used to upgrade their development system so they can continue development of this software.

If you are preparing a fresh copy of Red Hat Linux 5.0, installation of PostgreSQL is simple. Just select the SQL server from the “Components to Install” menu at setup time, then later in the setup indicate that the PostgreSQL server should be started at boot in the “Services” menu. The software will be installed, a “postgres” user will be added as a DataBase Administrator (DBA), and startup scripts and links will be installed under /etc/rc.d to spawn the database server in the appropriate system run levels (supported by the Unix init utility).

If you installed a copy of Red Hat Linux 5.0 without loading the database components, you can install them at a later time if you have the RPMs containing PostgreSQL. You can find the RPMs on your distribution CD or off the Red Hat FTP site. If you have them, they can be installed and configured by running the following commands as root:

rpm -Uvh postgresql-6.2.1-7.i386.rpm
rpm -Uvh postgresql-devel-6.2.1-7.i386.rpm
rpm -Uvh postgresql-data-6.2.1-7.i386.rpm
ntsysv
The commands above can be easily cut and pasted into a shell window.

The ntsysv command will allow you to select which system services are started at boot time (it creates links from files in /etc/rc.d/init.d to files in /etc/rc.d/rcX.d). Make sure that both PostgreSQL and the Apache Web server are selected.

If you are running an earlier release of Red Hat Linux (4.2 or below), you can still install an RPM containing PostgreSQL. Look for postgresql-6.2.1-1.i386.rpm in the contrib areas on the ftp.redhat.com site.

If you are running another Unix variant, you must download and compile PostgreSQL from their Web site's download areas. This may not be such a bad thing because a newer version of the database is available that now supports a larger subset of ANSI SQL (specifically, it now implements subselects).

An ANSI C compiler is required for the preparation of PostgreSQL. Most modern versions of commercial UNIX do not include such compilers (down with the un-bundling of UNIX!). If you do not have a C compiler for your proprietary UNIX system, Linux presents a simpler solution.

GNU Gcc is available for many platforms, and it can be used to prepare PHP. Check the Frequently Asked Questions list (FAQ) for your operating system to see if Gcc is available (such FAQs are easily found with the common internet search engines).

Please also note that binaries produced from C code compiled by Gcc will rarely be as fast as binares produced from the same code but compiled by the OS vendor's native compiler. Performance under high utilization conditions may increase with such native compilers.

Installing PHP

PHP is available from the PHP 3.0 Site. The code only recently emerged from beta testing, and is available for download. The name of the file is php-3.0RC3.tar.gz. You must obtain this file or a later version, if one is available.

PHP supports a number of database servers in addition to PostgreSQL. These include Adabas, mSQL, MySQL, Oracle, Sybase, and ODBC. PHP now also supports LDAP directory services and the IMAP mail protocol. More information about PHP is available in the FAQ at their site.

There are two ways to configure PHP. It can be used either as a CGI binary, or as a module loaded at run-time for a supported Web server (Apache, the Netscape servers, and Microsoft IIS).

Loading PHP as an Apache module has a number of benefits. With the CGI approach, the entire PHP parser is loaded, executed, then terminated every time a browser accesses PHP on the server. When loaded as a module, the PHP parser becomes an integrated part of the Apache run-time environment. Because the PHP memory image is not loaded and destroyed by each access, the performance of the module configuration is significantly greater.

However, there have been a number of updates to the Apache Web server issued by Red Hat. Each time such an update occurs, the PHP-enabled Web server must be rebuilt from source. Such an arrangement can quickly become tedious.

In compiling PHP as a CGI binary, PHP is built in such a way that it is entirely separate from the Web server installation. Updates to the Web server can be applied without fear of disturbing PHP. This configuration should be appropriate for all but the most high-traffic Web sites.

Running PHP as a CGI binary could create a serious security problem. Apache allows files -- named .htaccess by default -- to limit access to Web pages to specific IP addresses or to users who supply a valid username-password combination. PHP does not honor .htaccess files, and thus, if installed as a CGI binary, it can be used to read every Web page under the server's document root or users' directories, regardless of the access control that has been specified in .htaccess. Luckily, this is not an issue with the Apache module version of PHP.

Ultimately, users who are not overly concerned with the security of their Web server's document root directory tree and who do not anticipate high volume usage of PHP-enabled Web pages, should install the CGI version of the parser. All others should take the extra time to install PHP as an Apache module.

To install PHP as a CGI binary on a Red Hat Linux 5.0 system, issue the following commands as root (superuser):

tar xvzf php-3.0RC3.tar.gz
cd php-3.0RC3
CFLAGS="-s -O3 -I/usr/include/pgsql" ./configure --disable-debug \
	--with-pgsql=yes --with-system-regex
make
cp php /home/httpd/cgi-bin
The commands above can be easily cut and pasted into a shell window.

The rest of this document assumes the use of the CGI version.

If you wish to prepare the Apache module version of PHP, you must obtain the source for the latest Apache RPM (<URL:ftp://ftp.redhat.com/pub/redhat/redhat-5.0/updates/SRPMS/apache-1.2.5-1.src.rpm>). To compile and install the modified httpd, stop your Web server, then place the Apache source RPM and the PHP package in the same directory and issue the following commands as root:

mkdir apache
cd apache
rpm2cpio ../apache-1.2.5-1.src.rpm | cpio -i
tar xvzf apache_1.2.5.tar.gz
sed 's/apache-1\.2\.4/apache_1.2.5/' apache_1.2.4-rh.patch | patch
cd ..
tar xvzf
 php-3.0RC3.tar.gz
cd php-3.0RC3
CFLAGS="-s -O3 -I/usr/include/pgsql" ./configure --disable-debug \
        --with-pgsql=yes --with-system-regex \
        --with-apache=../apache/apache_1.2.5
make
make install
cd ../apache/apache_1.2.5/src
echo 'Module php3_module mod_php3.o' >> Configuration
sed 's/^EXTRA_LIBS.*$/EXTRA_LIBS=-L. -Lmodules\/extra -L..\/modules\/extra -lphp3 -lgdbm -ldb -lm -ldl -lcrypt -lpq/' Configuration | \
        sed 's/Rule WANTHSREGEX=default/Rule WANTHSREGEX=no/' | \
        sed 's/EXTRA_CFLAGS=/EXTRA_CFLAGS=-s -O3/' > Configuration1
cp Configuration1 Configuration
./Configure
make
strip httpd
cp -f httpd /usr/sbin
chmod 755 /usr/sbin/httpd
echo 'AddType application/x-httpd-php3 .php3' >> /etc/httpd/conf/srm.conf
echo 'AddType application/x-httpd-php3-source .phps' >> /etc/httpd/conf/srm.conf
The commands above can be easily cut and pasted into a shell window.


Flash: If you are running Red Hat 6.0 or above, you no longer need to recompile your webserver to install native PHP/PostgreSQL support for Apache; the same effect can be obtained by loading shared libraries. If you do need to rebuild your Apache webserver for other reasons, see one of the later sections sections below. Otherwise, you can find RPM-based commands for loading PHP on Red Hat 6.1 Apache directly below:
/etc/rc.d/init.d/httpd stop
rpm -Uvh apache-1.3.9-8.i386.rpm #(If not already installed.)
rpm -Uvh php-3.0.12-6.i386.rpm
rpm -Uvh php-pgsql-3.0.12-6.i386.rpm
sed 's/^#LoadModule php3_module/LoadModule php3_module/
	s/^#AddModule mod_php3.c/AddModule mod_php3.c/
	s|^#AddType application/x-httpd-php3 .php3|AddType application/x-httpd-php3 .php3|' \
	/etc/httpd/conf/httpd.conf > /etc/httpd/conf/httpd.conf.avecphp
echo 'AddType application/x-httpd-php3 .php3' >> \
	/etc/httpd/conf/httpd.conf.avecphp
mv /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.sansphp
mv /etc/httpd/conf/httpd.conf.avecphp /etc/httpd/conf/httpd.conf
/etc/rc.d/init.d/httpd start
Flash: Following is a script to build PHP on Red Hat 6.1, with Apache 1.3.9. Place the Apache Source RPM and the php-3.0.12.tar.gz file in the current directory and run the script. Please report any problems with this script.
/etc/rc.d/init.d/httpd stop
mkdir apache
cd apache
rpm2cpio ../apache-1.3.9-4.src.rpm | cpio -i
tar xvzf apache_1.3.9.tar.gz
cd apache_1.3.9
for x in ../*.patch
do
	patch -p1 < $x
done
mv ../mod_bandwidth.c .
CFLAGS="-O2" LDFLAGS="-s" ./configure --prefix=/usr \
	--with-layout=RedHat \
	--add-module=mod_bandwidth.c \
	--enable-module=all \
	--disable-rule=WANTHSREGEX \
	--disable-module=auth_dbm \
	--with-perl=/usr/bin/perl \
	--enable-suexec \
	--suexec-docroot=/home/httpd/html \
	--suexec-caller=nobody
cd ../..
tar xvzf php-3.0.12.tar.gz 
cd php-3.0.12
# Remove the "--with-pgsql" line below to disable PostgreSQL.
# Remove the "--with-sybase-ct" line below to disable Sybase support.
LDFLAGS="-s" CFLAGS="-O2 -I/usr/include/pgsql" ./configure --disable-debug \
	--with-system-regex \
	--with-pgsql \
	--with-sybase-ct \
	--with-apache=../apache/apache_1.3.9
make
make install
cd ../apache/apache_1.3.9
CFLAGS="-O2" LDFLAGS="-s" ./configure --prefix=/usr \
	--with-layout=RedHat \
	--add-module=mod_bandwidth.c \
	--enable-module=all \
	--disable-rule=WANTHSREGEX \
	--disable-module=auth_dbm \
	--with-perl=/usr/bin/perl \
	--enable-suexec \
	--suexec-docroot=/home/httpd/html \
	--suexec-caller=nobody \
	--activate-module=src/modules/php3/libphp3.a
make
cd src
cp -f httpd /usr/sbin
echo 'AddType application/x-httpd-php3 .php3' >> /etc/httpd/conf/srm.conf
cd /etc/httpd/conf
sed 's/^LoadModule/#LoadModule/
s/^ClearModule/#ClearModule/
s/^AddModule/#AddModule/' httpd.conf > httpd.conf.avecphp
mv httpd.conf httpd.conf.sansphp
mv httpd.conf.avecphp httpd.conf
cd
/etc/rc.d/init.d/httpd start
Flash: Following is a script to build PHP on Red Hat 6.0, with Apache 1.3.6. Place the Apache Source RPM and the php-3.0.12.tar.gz file in the current directory and run the script. Please report any problems with this script.
/etc/rc.d/init.d/httpd stop
mkdir apache
cd apache
rpm2cpio ../apache-1.3.6-7.src.rpm | cpio -i
tar xvzf apache_1.3.6.tar.gz
cd apache_1.3.6
for x in ../*.patch
do
	patch -p1 < $x
done
CFLAGS="-O2" LDFLAGS="-s" ./configure --prefix=/usr \
	--with-layout=RedHat \
	--enable-module=all \
	--disable-rule=WANTHSREGEX \
	--disable-module=auth_dbm \
	--with-perl=/usr/bin/perl
cd ../..
tar xvzf php-3.0.12.tar.gz 
cd php-3.0.12
# Remove the "--with-pgsql" line below to disable PostgreSQL.
# Remove the "--with-sybase-ct" line below to disable Sybase support.
LDFLAGS="-s" CFLAGS="-O3 -I/usr/include/pgsql" ./configure --disable-debug \
	--with-system-regex \
	--with-pgsql \
	--with-sybase-ct \
	--with-apache=../apache/apache_1.3.6
make
make install
cd ../apache/apache_1.3.6
CFLAGS="-O2" LDFLAGS="-s" ./configure --prefix=/usr \
	--with-layout=RedHat \
	--enable-module=all \
	--disable-rule=WANTHSREGEX \
	--disable-module=auth_dbm \
	--with-perl=/usr/bin/perl \
	--activate-module=src/modules/php3/libphp3.a
make
cd src
cp -f httpd /usr/sbin
echo 'AddType application/x-httpd-php3 .php3' >> /etc/httpd/conf/srm.conf
cd /etc/httpd/conf
sed 's/^LoadModule/#LoadModule/
s/^ClearModule/#ClearModule/
s/^AddModule/#AddModule/' httpd.conf > httpd.conf.avecphp
mv httpd.conf httpd.conf.sansphp
mv httpd.conf.avecphp httpd.conf
cd
/etc/rc.d/init.d/httpd start
Flash: Following is a script to build PHP on Red Hat 5.2, with Apache 1.3.3. This script is much different from the earlier script for Red Hat 5.0.
/etc/rc.d/init.d/httpd stop
mkdir apache
cd apache
rpm2cpio ../apache-1.3.3-1.src.rpm | cpio -i
tar xvzf apache_1.3.3.tar.gz
cd apache_1.3.3
for x in ../*.patch
do
	patch -p1 < $x
done
CFLAGS="-O2" LDFLAGS="-s" ./configure --prefix=/usr \
	--libexecdir=/usr/lib/apache \
	--sysconfdir=/etc/httpd/conf \
	--serverroot=/etc/httpd \
	--datadir=/home/httpd \
	--includedir=/usr/include/apache \
	--logfiledir=/var/log/httpd \
	--localstatedir=/var \
	--runtimedir=/var/run \
	--proxycachedir=/var/cache/httpd \
	--enable-module=all \
	--disable-rule=WANTHSREGEX
cd ../..
tar xvzf php-3.0.7.tar.gz 
cd php-3.0.7
# Remove the "--with-pgsql" line below to disable PostgreSQL.
# Remove the "--with-sybase-ct" line below to disable Sybase support.
LDFLAGS="-s" CFLAGS="-O3 -I/usr/include/pgsql" ./configure --disable-debug \
	--with-system-regex \
	--with-pgsql \
	--with-sybase-ct \
	--with-apache=../apache/apache_1.3.3
make
make install
cd ../apache/apache_1.3.3
CFLAGS="-O2" LDFLAGS="-s" ./configure --prefix=/usr \
	--libexecdir=/usr/lib/apache \
	--sysconfdir=/etc/httpd/conf \
	--serverroot=/etc/httpd \
	--datadir=/home/httpd \
	--includedir=/usr/include/apache \
	--logfiledir=/var/log/httpd \
	--localstatedir=/var \
	--runtimedir=/var/run \
	--proxycachedir=/var/cache/httpd \
	--enable-module=all \
	--disable-rule=WANTHSREGEX \
	--activate-module=src/modules/php3/libphp3.a
make
cd src
cp -f httpd /usr/sbin
echo 'AddType application/x-httpd-php3 .php3' >> /etc/httpd/conf/srm.conf
cd /etc/httpd/conf
sed 's/^LoadModule/#LoadModule/
s/^ClearModule/#ClearModule/
s/^AddModule/#AddModule/' httpd.conf > httpd.conf.avecphp
mv httpd.conf httpd.conf.sansphp
mv httpd.conf.avecphp httpd.conf
cd
/etc/rc.d/init.d/httpd start
Flash:For Red Hat Linux 5.2, a binary copy of the Apache 1.3.3 web server, prepared with PHP 3.0.7 with PostgreSQL support, can be downloaded here: httpd.gz
The file is 718,940 bytes when uncompressed. It should be installed as /usr/sbin/httpd. All the module directives must be removed from httpd.conf, and an AddType directive must be applied to srm.conf. See the latter stages of the above script for details (run everything from the last “echo” command down). This installation will not break RPM.

A slightly smaller binary image will be created if the -O2 level of optimization is used rather than -O3 (although the binary will not run as fast. Here, the -O3 option tells the compiler to generate inline functions where appropriate).

Establishing a Database

The following set of examples will create a database that implements a Web-enabled grocery shopping list.

Once PostgreSQL is installed, users should be created that will maintain the databases. Those users must also be registered with the PostgreSQL server.

Flash: If you are working from a fresh installation of Red Hat 6.0 or above, the PostgreSQL database server is not initialized properly and the postmaster daemon will not execute. To initialize it, run the following commands as root:
su - postgres
PGDATA=/var/lib/pgsql PGLIB=/usr/lib/pgsql initdb
exit
/etc/rc.d/init.d/postgresql start

The commands below will add a user named luser to a Red Hat system, then register both luser and nobody with the database server.

If you want to use a different account name than luser, just substitute the modified name for luser in the rest of this document.

As superuser, issue the following commands:

# useradd luser
# su - postgres
# createuser luser
  Enter user's postgres ID or RETURN to use unix user ID: 500 -> (enter)
  Is user "luser" allowed to create databases (y/n) y
  Is user "luser" allowed to add users? (y/n) n
  createuser: luser was successfully added
# createuser nobody
  Enter user's postgres ID or RETURN to use unix user ID: 99 -> (enter)
  Is user "nobody" allowed to create databases (y/n) n
  Is user "nobody" allowed to add users? (y/n) n
  createuser: nobody was successfully added
  don't forget to create a database for nobody
# exit

(The text in boldface above indicates the commands you must enter, while the text in the normal typeface indicates the system's response).

Don't forget to set the UNIX password for the “luser” account with the passwd command.

The user nobody needs to be registered with the database because the Web server runs under this userid. This action will enable the Web server to run queries against the database server.

Next, login as “luser” and run createdb grocery. This command creates a database. Older DBMS applications, like dBase and its derivatives, called tabular collections of data databases. Relations could be established between these databases. This is not so in PostgreSQL. In this new paradigm, databases contain tables, and these tables contain the tabular data. Relations are established between tables that lie within a database. The previous command only creates the database; the tables are created in a later step.

Now, while logged in as “luser,” enter the command to initiate the interactive SQL interpreter, namely: psql grocery.

The SQL interpreter will print a welcome message:

Welcome to the POSTGRESQL interactive sql monitor:
Please read the file COPYRIGHT for copyright terms of POSTGRESQL

   type \? for help on slash commands
   type \q to quit
   type \g or terminate with semicolon to execute query
You are currently connected to the database: grocery

grocery=>

SQL commands may be entered directly at the prompt. They may span many lines if necessary. The command is not issued until a semicolon is encountered.

Enter the following text at the prompt to create a table to hold the grocery list:

CREATE TABLE list (
        item                    TEXT,
        vendorcode              INT,
        quantity                INT );

This command creates a table named “list,” which is composed of three fields. The fields correspond to columns in a spreadsheet (although a PostgreSQL database can be magnitudes larger than the largest spreadsheets). The names of the fields are “item,” “vendorcode,” and “quantity.” The fields have an associated data type. For instance, the vendorcode and quantity fields are both defined as type integer (they can only contain numbers, not text). The item field is of type text, and its size can be of any length.

SQL is not case-sensitive so any combination of upper- and lower-case characters can be used with its commands. The style used here follows that of most popular tutorial texts on database design, which allows one to easily distinguish the SQL reserved words from the variable names.

The following command creates another table named “vendors” with two fields, one for integers, the other for text.

CREATE TABLE vendors (
        vendorcode              INT,
        vendorname              TEXT );

The tables have been created and they are empty. The SQL INSERT command can be used to populate the tables with several values, as shown:

INSERT INTO vendors VALUES (100, 'Super Grocer');
INSERT INTO list VALUES ('Root Beer', 100, 3);

The previous insert syntax is valid and is very useful for one-time jobs. However, if columns are added to or removed from the tables, the above insert syntax will most likely fail. An alternate syntax is available that can specify the columns into which data will be inserted. Any columns not mentioned will receive NULL values (if such values are allowed).

INSERT INTO vendors (vendorcode, vendorname)
	VALUES (101, 'General Department Store');
INSERT INTO vendors (vendorcode, vendorname)
	VALUES (102, 'General Auto Parts');
INSERT INTO list (item, vendorcode, quantity)
	VALUES ('Ice Cream', 100, 1);
INSERT INTO list (item, vendorcode, quantity)
	VALUES ('Napkins', 101, 50);
INSERT INTO list (item, vendorcode, quantity)
	VALUES ('Spark Plugs', 102, 4);

The data can be examined with the SQL SELECT command, shown here:

SELECT item, vendorcode, quantity FROM list;

The SQL interpreter should respond with:

  item       |vendorcode|quantity
  -----------+----------+--------
  Root Beer  |       100|       3
  Ice Cream  |       100|       1
  Napkins    |       101|      50
  Spark Plugs|       102|       4
  (4 rows)

Fields can be rearranged or omitted from the SELECT command by modifying the field names:

SELECT item, quantity FROM list;

  item       |quantity
  -----------+--------
  Root Beer  |       3
  Ice Cream  |       1
  Napkins    |      50
  Spark Plugs|       4
  (4 rows)

As an alternative, an asterisk can be used to indicate that all fields are desired from the SELECT query:

SELECT * FROM list;

  item       |vendorcode|quantity
  -----------+----------+--------
  Root Beer  |       100|       3
  Ice Cream  |       100|       1
  Napkins    |       101|      50
  Spark Plugs|       102|       4
  (4 rows)

An asterix can be very useful when querying a database with interactive sessions. However, its use should be avoided in programs, as columns inserted into or removed from the database will change the physical order of the data returned. Specifying the desired columns will prevent this effect.

Notice in the previous table that the numerical vendorcodes are printed rather than the more useful vendornames. The latter are actually contained within the “vendors” table:

SELECT * FROM vendors;

  vendorcode|vendorname
  ----------+------------------------
         100|Super Grocer
         101|General Department Store
         102|General Auto Parts
  (3 rows)

In the case of these two tables, however, the vendorcode field is not very useful. It would be much more appropriate to list the vendorname field from the vendors table when printing the list table. Such a thing is possible by establishing a relation:

SELECT list.item, vendors.vendorname, list.quantity
        FROM list, vendors
        WHERE list.vendorcode = vendors.vendorcode;

  item       |vendorname              |quantity
  -----------+------------------------+--------
  Root Beer  |Super Grocer            |       3
  Ice Cream  |Super Grocer            |       1
  Napkins    |General Department Store|      50
  Spark Plugs|General Auto Parts      |       4
  (4 rows)

In the example above, the FROM clause specifies that two tables are to be used. The WHERE clause specifies the conditions for the relation. Relations such as these are called joins in SQL.

When two tables are used in a SELECT statement, such as the one above, the tablename. fieldname syntax is used to distinguish between the tables and fields.

There are many more options to the SQL SELECT statement; so many that SELECT is the most powerful command in the SQL language. Here is a slight variation on the previous example:

SELECT list.item, vendors.vendorname, list.quantity
        FROM list, vendors
        WHERE list.vendorcode = vendors.vendorcode
        ORDER BY item;

  item       |vendorname              |quantity
  -----------+------------------------+--------
  Ice Cream  |Super Grocer            |       1
  Napkins    |General Department Store|      50
  Root Beer  |Super Grocer            |       3
  Spark Plugs|General Auto Parts      |       4
  (4 rows)

Here the ORDER BY clause causes the output to be sorted alphabetically by the item field.

It is also possible to use UNIX Regular Expressions in SELECT statements with PostgreSQL (a feat which not many commercial database servers can equal):

SELECT list.item, vendors.vendorname, list.quantity
        FROM list, vendors
        WHERE list.vendorcode = vendors.vendorcode
	AND list.item ~ '^[I-N]'
        ORDER BY item;

  item     |vendorname              |quantity
  ---------+------------------------+--------
  Ice Cream|Super Grocer            |       1
  Napkins  |General Department Store|      50
  (2 rows)

One interesting point about SQL join operations is that records in one table that will not join with records in the other are omitted. If you run the following INSERT command:

INSERT INTO list (item, vendorcode, quantity)
	VALUES ('African Violet', 103, 1);

And then immediately follow it with the previous SELECT statement:

SELECT list.item, vendors.vendorname, list.quantity
        FROM list, vendors
        WHERE list.vendorcode = vendors.vendorcode
        ORDER BY item;

You will notice that the “African Violet” row in the “list” table was not printed. However, if you run the following command (and then re-run the SELECT):

INSERT INTO vendors (vendorcode, vendorname)
	VALUES (103, 'ACME Plant Store');

It will appear.

Records can be modified with the SQL of the following syntax:

UPDATE list SET item = 'African Violets' WHERE vendorcode = 103;

The above command modifies all records from vendor 103. There is only a single item in the table with this vendor, so the behavior in this case is correct. A more restrictive WHERE clause might be required were there additional records with the same vendor.

If you wish to delete rows from the database, you can use the SQL DELETE command:

DELETE FROM list WHERE item = 'African Violet';
DELETE FROM vendors WHERE vendorcode = 103;

Be careful, because “DELETE FROM list;” would wipe out all the data, leaving the table empty.

In a production environment -- where tables contain a large number of rows -- SQL operations may be faster if an “index” is defined. In this case, the commands to create the indexes are:

CREATE INDEX listtab ON list (vendorcode);
CREATE UNIQUE INDEX vendortab ON vendors (vendorcode);

Notice that a unique index is created on the “vendors” table, because each vendor will have a unique vendor code. A unique index will prevent duplicate entries in the associated column; this type of restriction is called a constraint. In addition to unique constraints, there are four additional types of constraints: primary keys, foreign keys, check constraints, and NOT NULL constraints. None of these is discussed here.

PostgreSQL keeps copies of modified or deleted rows in a database. This allows the database to be restored to the state it had at a previous date. Unfortunately, this also means that a large amount of storage could be consumed by inactive data. To clean your database of such inactive data, use the commands:

VACUUM list;
VACUUM vendors;

In the next chapter, the Web server will be connecting to the database to perform SELECT and INSERT operations. Under Red Hat Linux, the Web server process runs under user identity “nobody.” To grant this user permission to SELECT from and INSERT into the database, run the following commands:

REVOKE ALL ON list FROM nobody;
GRANT SELECT ON list TO nobody;
GRANT INSERT ON list TO nobody;
REVOKE ALL ON vendors FROM nobody;
GRANT SELECT ON vendors TO nobody;
GRANT INSERT ON vendors TO nobody;
GRANT ALL ON list TO luser;
GRANT ALL ON vendors TO luser;

The REVOKE ALL commands above remove all access permissions. Using such a REVOKE before a GRANT ensures that no previous permissions remain to the user.

If you are finished with the SQL interpreter, you can log out of it by typing: \q.

The pg_dump command is a useful utility that allows easy backup and transport of PostgreSQL databases. It generates a text file composed of the SQL commands required to recreate a database. To dump the grocery database, use the shell command line: pg_dump grocery > db.out

This file can be manipulated with a normal text editor. To reload the database, enter the shell command: psql -e grocery < db.out

As a last point, psql uses the GNU Readline library. It is configured by default to accept Emacs keystrokes to edit the command line (that is, the up and down arrows cycle through the previous and next commands, Control-A moves to the beginning of the line, and so forth.). However, if you write the single line: set editing-mode vi into a file named .inputrc located in your account home directory, then Vi commands can be used instead. Be warned that this changes all programs that use GNU Readline, including Bash and Gdb.

Using SELECT from the Web

This section assumes that you have a firm working knowledge of HTML. If this is not the case, you might want to review the NCSA Beginner's Guide to HTML.

If you have installed your PHP binary and have entered the database commands described above, you are ready to build Web pages that use SQL.

PHP, and its earlier namesake, PHP/FI, use a C-like structured programming language that is embedded directly within HTML. PHP is used as a document preprocessor (much like the preprocessing stage of a C compiler, except that it is much more powerful).

Copy the following HTML into a file on your system named /home/httpd/html/dbprint.php3 (if you are not running Red Hat Linux, place the file in your Web server's document-root directory):

<HTML>
<HEAD>
<TITLE>View Database Records</TITLE>
</HEAD>
<BODY>

<DIV ALIGN="center">

<P>View Database Records</P>

<TABLE BORDER="0">

<TR>
<TH>item</TH>
<TH>vendorname</TH>
<TH>quantity</TH>
</TR>

<?PHP

        $conn = pg_Connect("", "", "", "", "grocery");

        if (!$conn) {

                echo "An error occurred.\n";
                exit;
        }

        $result = pg_Exec($conn,
                "SELECT list.item, vendors.vendorname, list.quantity
                        FROM list, vendors
                        WHERE list.vendorcode = vendors.vendorcode

                        ORDER BY list.item;");

        if (!$result) {

                echo "An error occurred.\n";
                exit;
        }

        $num = pg_NumRows($result);
        $i = 0;

        while ($i < $num) {

                echo "<TR><TD>";
                echo pg_Result($result, $i, "item");
                echo "</TD><TD>";
                echo pg_Result($result, $i, "vendorname");
                echo "</TD><TD>";
                echo pg_Result($result, $i, "quantity");
                echo "</TD></TR>";
                $i++;
        }

        pg_FreeResult($result);
        pg_Close($conn);
?>

</TABLE>

</BODY>
</HTML>

If you are running Red Hat Linux and you have installed PHP as /home/httpd/cgi-bin/php, you can now use a Web browser to view your database by opening the URL: http://localhost/cgi-bin/php/dbprint.php3

If you have installed the Apache module version of PHP, instead use the URL: http://localhost/dbprint.php3.

You can substitute your Fully Qualified Domain Name (FQDN) for localhost if you want to view the page from browsers that are running on different hosts.

Assuming that everything runs smoothly, You should see an HTML table that looks like this:

View Database Records

item vendorname quantity
African Violet ACME Plant Store 1
Ice Cream Super Grocer 1
Napkins General Department Store 50
Root Beer Super Grocer 3
Spark Plugs General Auto Parts 4

The “.php3” file-name extension is a normal convention for files marked with PHP tags. It is not, however, a requirement in this case; the files could have had an .html extension with no effect upon the operation of the PHP parser.

PHP also has a directive that is similar to Server Side Includes (SSI) supported by most popular Web servers. If the command include("/some/path/to/a/file.html") is encountered in a PHP block, the specified file will be inserted and parsed by PHP. There is even a phpIncludePath variable that can be set that will allow the Web developer to dispense with path names. Conventional SSI directives will not be processed with the CGI version of PHP.

PHP will also read files in users' home directories, following the normal conventions of Unix Web servers. For example, if you copied dbprint.html to ~luser/public_html/, you could open the document in its new location with the URL: http://localhost/cgi-bin/php/~luser/dbprint.php3

The algorithm and syntax of the above HTML example are relatively simple. They are taken almost directly from the PHP/FI documentation.

Notice first the enclosing <?PHP and ?> tags that surround the non-HTML language. These mark the beginning and end respectively of PHP language statements. You may insert PHP statements delimited by these markers as many times as you like in your HTML file.

Here, these PHP statements do the following:



Using INSERT from the Web

This section assumes that you have a firm working knowledge of HTML forms. If this is not the case, you might want to review the NCSA Guide to Fill-Out Forms.

There are two main sections of HTML that will be used to add records to the database. First, an HTML form must be constructed so that the information can be easily entered by the browser. Second, the information must be returned and processed by PHP in order to add it to the table.

The implementation of the form, and specifically the vendor field, presents a design dilemma. The SELECT form tag is the most obvious HTML form element to use to present a pre-defined vendor list.

Should this list be hard-coded in the HTML? If another vendor is added to the “vendors” table, the HTML will not be automatically updated, and users will not be able to enter data against the new vendor.

The form elements for the vendor SELECT tag could alternately be generated by PHP each time the form is accessed. The drawback to this method is that the server load will be increased.

Another approach would be to generate the HTML for the form whenever the “vendors” table is updated. This could be accomplished in a variety of ways, some of which stem from the fact that PHP can be called from a Unix shell prompt as well as from a CGI environment.

Below, I'm presenting an alternative; each time the page for the data-entry form is accessed, PHP inserts data from the “vendors” table to build the SELECT list.

Copy the following HTML into a file on your system named /home/httpd/html/dbform.php3 (if you are not running Red Hat Linux, place the following in your Web server's document-root directory):

<HTML>
<HEAD>
<TITLE>Insert Database Record</TITLE>
</HEAD>
<BODY>

<DIV ALIGN="center">

<H1>Add Database Record</H1>

<FORM METHOD="post" ACTION="/cgi-bin/php/dbinsert.php3">

<TABLE BORDER="0">

<TR>
<TD>New Item:</TD>
<TD><INPUT NAME="item"></TD>
</TR>

<TR>
<TD>Vendor:</TD>
<TD><SELECT NAME="vendor" SIZE="1">

<?PHP

        $conn = pg_Connect("", "", "", "", "grocery");

        if (!$conn) {
                echo "An error occurred.\n";
                exit;
        }

        $rc = pg_Exec($conn,
                "SELECT vendorcode, vendorname
                        FROM vendors
                        ORDER BY vendorname;");

        if (!$rc) {
                echo "An error occurred.\n";
                exit;
        }

	$i = 0;

        while ($result = @pg_fetch_array($rc, $i)) {
		echo "\n<OPTION VALUE=\"" .
			$result["vendorcode"] .
			"\">" .
			$result["vendorname"];
		$i++;
	}

        pg_FreeResult($rc);
        pg_Close($conn);
?>

</SELECT></TD>
</TR>

<TR>
<TD>Quantity:</TD>
<TD><INPUT NAME="quantity"></TD>
</TR>


</TABLE>

<INPUT TYPE="submit">

</FORM>

</BODY>
</HTML>

Noticing the ORDER BY clause in the above SELECT statement, an index on “vendors.vendorname” might be useful if the “vendors” table grows large.

Notice also that the pg_result() function was not used above. Instead, pg_fetch_array() was called as the condition of the while loop. pg_fetch_array() returns an associative array where the value for a column can be obtained by using the name of the column as the index of the array returned by the function call. pg_fetch_array() will return a warning message when its result set is exhausted, so the "@" is placed at the beginning of the statement, which suppresses warnings for this statement only.

The above PHP code extracts each vendorname in alphabetical order and prints it to the browser with a prefix of OPTION (as is required by the SELECT tag).

You can now view your form with the URL: http://localhost/cgi-bin/php/dbform.php3

Assuming that everything runs smoothly, you should see an HTML form that looks like this:

New Item:
Vendor:
Quantity:

(This form is not active, and is not attached to a database server.)

Now that the “front end” is complete, a “back end” must be implemented.

Copy the following HTML into a file on your system named /home/httpd/html/dbinsert.php3 (if you are not running Red Hat Linux, place the file in your Web server's document-root directory:

<HTML>
<HEAD>
<TITLE>Confirm Database Insert</TITLE>
</HEAD>
<BODY>

<DIV ALIGN="center">

<?PHP

        $conn = pg_Connect("", "", "", "", "grocery");

        if (!$conn) {

                echo "An error occurred.\n";
                exit;
        }

        pg_Exec($conn,
                "INSERT INTO list (item, vendorcode, quantity)
                        VALUES ('$item', $vendor, $quantity);");

        pg_Close($conn);
?>

Database Updated

</BODY>
</HTML>

With complete information for the item, vendorcode and quantity fields of the “list” table, the data can then be inserted by the last pg_Exec() call.

Notice that the variable name given to the HTML SELECT tag in dbform.php3 is available to PHP in dbinsert.php3. The same is true of the “item” and “quantity” form elements that compose the SQL insert operation.

The above PHP performs the SQL insert transactions and sends a confirmation message to the browser. If these .php3 files were describing a high-production data entry interface, it might be useful to copy dbform.php3 onto the end of dbinsert.php3 so that the form is continually presented.

Conclusion

In spite of claims made by Microsoft, a Linux database server is actually much less expensive than SQL under NT:

A Windows NT server costs approximately $1,000 (or more, depending upon user licenses). The Microsoft Backoffice package can add between $5,000 to $10,000 to the price, and what you've bought is a proprietary system with standards dictated by this monopolistic vendor.

The GNU Public License (GPL) release of Red Hat Linux is available from Linux Systems Labs for the cost of the distribution media ($1.95). If you don't like Linux, all of the software mentioned above can be ported to other Unix platforms (some of it even runs on NT).

Linux also includes an unlimited-seat SMB server, SQL server, dialup-server capability, FTP, WWW, Telnet, X Window System, NFS, and electronic mail servers. Some of these items are very pricey on Windows NT.

If you need an inexpensive, reliable Web database solution that offers the flexibility of Unix, our combination of Red Hat Linux, PHP, and PostgreSQL is one excellent way to go.

Author Biography

Charles Fisher is a writer and consultant who specializes in Linux. He has a home page that describes his personal and professional interests.


Edited by Becca Thomas / editor at unixworld.com