db_update
-- Controlling UNIX Enterprise Database Releases
Ed Schaefer
Application software changes generally require database schema
updates. Questions about a software release often arise. Is the
database schema in sync with the application object code? Was a
previously required software version installed? Were there any errors
during the update? If so, have these errors endangered the software
release and should the update be reversed? Answering these questions
requires a concise, consistent method for updating, as well as reversing
changes.
This article presents a Korn shell template, db_update, for controlling
enterprise database changes (see Listing 1). db_update, with the
database vendor's access utility, controls a release by defining
the previous and current release and executing Structure Query Language
(SQL) statements against the database. I call db_update a template
because a base version of db_update exists in my Revision Control
System, RCS. I create a separate shell script based on the release
name (e.g., 2001.1.0.chg.ss), place my desired database changes
in this script, and check this new file into RCS. In this article,
I refer to the script as "db_update".
After covering the major database vendors and portability issues,
I will discuss the following db_update program design in detail:
- If no command-line option exists, execute the usage function
and terminate.
- If the database engine is not functioning, terminate with an
error.
- If the database cannot be opened, terminate with an appropriate
error.
- Determine whether the command-line option is an update or an
undo.
- If other users are using the database, prompt whether to continue.
- If continuing, get the Revision Control System, RCS, number
of the script.
- If executing an update and the previous version is not installed,
terminate with an appropriate error.
- If executing an update and the current version is already installed,
terminate with an appropriate error.
- If executing an undo and the current version is not yet installed,
terminate with an appropriate error.
- Execute the update or undo SQL statements.
- Register the completed update or undo.
Parametrizing and Portability of SQL Utilities
The three major UNIX database vendors provide their own access
utilities: Sybase has isql, Oracle relies on sqlplus, and Informix
bundles dbaccess. Typically, these utilities are called from shell
scripts.
Utilizing these utilities in shell scripts is not new to Sys
Admin magazine. See "Using Oracle SQL in Shell Scripts"
by Kyle Gleed and Scott Tarvainen (May 1995, http://www.samag.com/documents/sam9505f/);
William Genosa's three-part series on Sybase systems administration
(September/October 1994, http://www.samag.com/documents/sam9409a/;
November/December 1994, http://www.samag.com/documents/sam9411k/;
and January/February 1995, http://www.samag.com/documents/sam9501h/),
and my article, "Sybaccess: A Menu Driven Interface to Sybase"
(July 1996, http://www.samag.com/documents/sam9607d/). In
the above articles, the access utilities are explicitly hard-coded
in the scripts. Conversely, db_update, an Informix-based utility,
parametrizes the access utility and other pertinent data (e.g.,
database name, kill command, etc).
Because most industrial-strength UNIX utilities support pipes
and here documents, porting db_update to another database environment
is simplified by modifying the parameter definitions. Consider the
following Informix query example:
DBNAME="testdb"
INTERFACE_CMD="dbaccess"
DBCOMMAND="eval $INTERFACE_CMD -e $DBNAME"
$DBCOMMAND << MSG
SELECT * FROM test_table
MSG
The following Sybase example emulates the same query:
SERVERNAME="TEST_SERVER"
USER="t_user"
PASSWD="t_pass"
DBNAME="testdb"
DBCOMMAND="eval isql -S $SERVERNAME -U $USER -P $PASSWD"
$DBCOMMAND << MSG
use $DBNAME
go
SELECT * FROM test_table
go
MSG
Since Sybase and Microsoft's SQL server share a common ancestry,
the above script also works on Windows NT using the Mortice Kern (MKS)
Korn shell accessing the SQL server database environment.
After reviewing the db_revision requirements, the rest of this
article delves into the db_update script.
Reviewing the db_revision Table
For db_update to function correctly, each database accessed stores
current and prior revision information in a db_revision table:
create table db_revision
(
version char(20), # primary key
install_date date,
uninstall_date date,
installed char(1),
rcs_version char(10)
);
This table stores the present database version being installed, as
well as any previously installed versions. For example, the present
version might be 2001.3.0, the third release of 2001. The installed_date
and uninstalled_date columns are the version install date and uninstall
date, respectively. Set the installed column to "Y" if the
version is installed and "N" if the undo is executed, effectively
uninstalling the changes.
Checking Engine and Database Health
After verifying the correct command-line argument count, perform
the database engine checks. Shell function database_engine_running
uses the robust Informix utility, onstat, to check whether the engine
is up. Because describing Informix database specifics is not my
intention, I hide the implementation details in ENGINE_UP variable.
While the database engine may be functioning, the database may
not exist or access may be denied. The database_check function simply
opens the database with a call to the dbaccess utility. If the call
fails, the UNIX exit code ($?) is non-zero and the program
terminates. The main_driver function then verifies the proper "update"
or "undo" command-line argument, sets the proper log name,
prompts the user to continue processing, and checks for users still
using the database.
Checking for Database Users
Often, users aren't allowed to use the database while major
schema changes are in progress. The check_session function checks
whether any users are still using the database, and if so, prompts
whether to terminate these users.
The Informix onstat utility determines whether user SQL sessions
are active and the Informix onmode utility is optionally used to
kill a session. Again, implementation details are hidden with shell
variables SESSIONS, ONSTAT_SES, ONSTAT_SQL, and KILL.
Retrieving the RCS Version
Because db_update is under RCS change control, the version of
this particular script is stored in the rcs_version function:
str="$Revision: 1.0 $"
Invoking this function returns the decimal RCS version number. This
RCS number is used later during registering the database version.
(Don't confuse the RCS version with the database version being
updated.)
Checking the Previous Version
The current version of the schema change (defined by shell variable
VERSION) cannot be installed if the previous required version (defined
by shell variable PREVIOUS) has not been installed. The check_previous
function performs this check.
The check_previous function returns immediately if PREVIOUS is
not required. If required, determine whether the PREVIOUS version
exists in the db_revision table. If it does not exist, PREVIOUS
was never installed, and terminate with a required version is
not installed error message.
Checking the Current Version
If this is an update, the check_current function determines whether
the current VERSION is already installed.
If a row exists in the db_revision table for VERSION and the installed
flag is "Y", set the UNIX exit code, $?, to 1 specifying
the current version is installed. Otherwise, set the exit code to
0 specifying the current version is not installed.
If this is an update and the VERSION is already installed, terminate
with an update previously installed prompt. If this is an
undo and the VERSION is not yet installed, terminate with a version
has not been installed message.
Explaining check_current Return Value
A number of db_update's functions (rcs_function, database_check,
database_engine_running) trap the standard output of commands, such
as echo, print, or printf, and use this output
as the function return value. All standard output is treated as
the return value, not just the echo or print command
concluding the function. In check_current, DBCOMMAND writes to standard
output, so avoid the standard output side effects by using the UNIX
exit code.
Updating/Undoing the Database Schema
After performing all the database and version checks, execute
the required database changes. Assuming the update option, place
in the change_schema function, the SQL required for the changes,
calls to external programs, or calls to other shell functions. The
example change_schema function only creates table, wo_table.
To undo a previously installed version, the reverse_schema function
contains the SQL to undo the changes made in function change_schema.
In this example, simply drop the wo_table.
Registering the Update
Once the Update completes, call the register_update function to
update the db_revision table. If this is an initial install, a new
record is created registering the new database version. If this
is an update after an undo, the VERSION record exists in db_revision,
but the installed column is "N". Register the update by
setting the installed column to "Y".
The db_revision's rcs_version column updates each time register_update
executes. Since db_update may be checked in and out of RCS many
times during development, it's often important to know what
RCS version of db_update updated a given database.
Registering the Undo
Similar to the update, once an undo option completes, executing
function register_undo registers the undo. Previous code checks
guarantee a record exists in db_revision for VERSION, so the installed
flag is set to "N".
Error Checking
DBCOMMAND_OTF redirects all pertinent schema output to a log file.
Once registration completes, I search the log file for string "Error";
any instance of "Error" is an alert that the Informix
engine/database had a possible execution problem and should be investigated.
Conclusion
The db_update template is one method of addressing, codifying,
and controlling a daunting database administration task. If you
are not running Informix, db_update's structure should ease
conversion to another environment.
Although it's beyond the scope of this article, the SQL statements
in the check_current, check_previous, register_update, and register_undo
functions are excellent stored procedure candidates.
Ed Schaefer is a frequent contributor to Sys Admin. He is a
DBA for Intel's Factory Integrated Information Systems, FIIS,
in Aloha, Oregon. Ed also hosts the UnixReview.com monthly Shell
Corner column. He can be reached at olded@ix.netcom.com.
|