STRUCTURED PROGRAMMING

Action at a Distance

Jeff Duntemann KG7JF

I have discovered a Great and Terrible Truth: New cars just aren't worth it anymore. It doesn't matter if they're produced here, in Japan, or in Outer Brungaria--what you get is in no way a fair return for the money you offer. Having spent close to a year poking around the auto industry, kicking tires and slamming doors, I've come away with a feeling of terrible disgust. A $10,000 car (what few remain) is a plastic toy, built so shoddily that I can only imagine them coming to pieces with virtually no resale value inside of five years. To get what I consider a car, you now have to lay out $25,000 or more--a sum I refuse to spend on a device that one drunk in an old Ford can destroy utterly in 200 milliseconds flat.

I've hesitated to speak my suspicions until now; that is, until Shakespeare rolled out of Gary Turney's body shop, gleaming like a brand new car, minus dents and plus a new coat of blinding white paint, with rechromed bumpers so shiny you can comb your hair in them. Another week at the reupholstery shop gave him brand new seats, a new headliner, new door panels, new armrests, and new electric-blue carpeting. With a start I realized that all that new vinyl had given Shakespeare a reborn dose of new-car smell.

Total cost on the Shakespeare project so far: $5700. For that I've gotten a car that looks new, smells new, roars like cars haven't roared since the Sixties--and is made of enough metal so that I have a fighting chance in a crackup against that drunk in the old Ford. I figure buying anything close to that quality in a '93 would cost $30,000.

It's true that there was a certain amount of effort involved. I made a few trips down to Dagley's Junkyard, searching out parts, and spent some time on the phone getting quotes on various jobs I didn't feel like fooling with myself. But I was amazed at the support network out there; there's even an outfit in Phoenix called Arizona Classic Chevelle Parts. You can buy almost anything for a Chevelle, either leftover GM stock or brand-new repro parts. I did buy a lot of parts and did lay out some money for work--the car originally cost me just $1800--but the end result is eminently worth the time and the money.

If you can't stand the thought of paying more for a car than some people still pay for a house, well, then support the other American auto industry, the one Detroit doesn't want you to know about: The people who recycle old cars and bring them back to life, often better than they were originally, and always for a fraction of what a similar vehicle would cost today.

Between Building and Buying

What makes software expensive is the same thing that makes cars expensive: time. The more people-hours spent on a product, the more that product will have to cost to make money in a given market. Time is now more expensive than it has ever been, drastically so. What this means for the auto industry is that a lot more people are going to be buying used cars. What it means for the software industry is that fewer and fewer products are going to be built "from scratch." The days when you could afford to create every single line of a major application in C or Pascal are mostly past, not only because time is expensive, but also because users' expectations have gone through the roof in recent years. A word processor now has to be a page-layout program, too--as unnecessary as this often is. (I still use WordPerfect 4.2 on my laptop. I'd use it in the office, too, if it weren't so hard to teach to my staff.)

Between buying a finished application in a can and writing it all yourself there is a lot of room for hooking together commercial subsystems with "glue" code of your own creation. This is now easier than it used to be, thanks to a growing family of well-defined application programming interfaces (APIs) that define how your code should communicate with a commercially purchased subsystem.

One of the very best APIs is the Windows spec for dynamic link libraries (DLLs). DLLs remove language dependency from library calls. The DLL API dictates the details of how a language calls its code, rather than leaving such details to the language itself. This allows a Windows DLL to be called from any language at all (assuming the language bothers to pay attention to the API), and gives a vendor of DLLs a much larger market than ever before. The same binary DLL can be called from Borland C, Turbo Pascal for Windows, Visual Basic, or Smalltalk/V Windows. Small language vendors can now count on a ready-made, third-party aftermarket immediately upon releasing their language to the public.

Other emerging APIs define calls to sound boards (MCI) and access to extended memory (DPMI). The one that has fascinated me most, however, concerns database management and the use of database engines in commercial applications.

Separating the Interface from the Database

Most people don't think of it in quite these terms, but the ascendance of standard APIs has made possible a remarkable concept called client-server database management. It has been a mainframer's concept most of its short life, but there's nothing about it that demands a mainframe. I've been doing it on my own desktop machine for a little while now, and it works well there, even if much of the concept's power is wasted.

From a height, this is the client-server idea: You take a coal chisel and split your database-oriented application right down the middle. The parts of the application specific to it (the menus, dialogs, help screens, and so on) stay in one chunk, and the general-purpose data-management code goes into the other chunk. The application-specific chunk handles interaction with the user, while the data-management code stores the data, performs searches and queries, and ensures that all one-to-many relationships among data files remain intact and valid.

You might say this reminds you of things like the Paradox Engine, B-Tree Filer, and other database engines of the sort that you link into your application to handle data management. Well, yes--except that in a client-server system you don't link the two pieces together. The application and the database engine are not part of a single .EXE file. They don't have to be in the same subdirectory. They don't even have to be on the same machine.

Now you're talking client-server.

In the client-server lexicon, your application is the client. The client application can be anywhere, as long as it is connected somehow to the machine that houses the database server. The connection could be a T1 mainframe link, an Ethernet-style local area network, or just a serial cable between two machines. Furthermore, nothing says that the client and server must be on separate machines. One of the most common links between clients and servers is the Windows DLL interface. (This is, in fact, the one I have tested most thoroughly myself.) What matters is not the nature of the connection, but the fact that the client and the server both speak the same language; that is, both understand the API that lies in common between them.

Enter SQL

There are a number of client-server APIs, most of which originated in mainframeland, or at least in places where networking is king. The usefulness of such APIs is directly proportional to how widely they are adopted, which in nearly all cases is not especially widely. Mainframe vendors have always been prone to the deadly NIH virus, for which there is no cure but extinction--and most would rather die than adopt an API that somebody else concocted.

Only one client-server API has come into anything that I'd call "general use:" IBM's Structured Query Language (SQL), which first appeared in the early '80s, and is still evolving. IBM has been good enough not to make silly ownership claims to the specification a la Ashton-Tate, and in consequence SQL is spreading rapidly through the database community as the lingua franca of client-server.

SQL is about as high-level as any API is ever going to get. It says nothing about calling conventions, stacks, or big-endian vs. little-endian conflicts. It is strictly a group of commands written as simple, English-like, 7-bit ASCII text. The client passes those commands across the connection to the server, which obeys them and passes back data to the client in response to the commands.

One interesting wrinkle here is that the "client" doesn't have to be an application. Because SQL is strictly text, and tolerably English-like, the client can be a human being typing at a terminal. This is analogous to working with DOS through Windows: Windows ordinarily handles the somewhat cryptic DOS command language for the user, but when the user really needs to, he or she can tickle the DOS-prompt icon, go out to the DOS prompt, and type commands directly to DOS.

Most use of SQL, however, is in the form of embedded SQL, a series of SQL commands embedded into the source code of some other host language like Basic, C, Pascal, or (God knows) Cobol. The application mediates between the end user and the SQL syntax and passes along the end user's wishes to the database server in the form of SQL commands.

I've sketched this out in Figure 1. Your application is Here, and the actual database-manager code is There. The gap between them is crossed by some connection or another; as I said before, it doesn't matter what the nature of that connection actually is. SQL commands are issued by your application, cross the gap, and are interpreted by the database manager's SQL-command interface. These commands typically set up a database query of some sort, which the database manager executes. The results of the query, usually rows from a table, are sent back over the gap to your application.

Keep in mind that SQL is an API. It's an interface language, not something that compiles to independent native code files. The database manager simply has a module that interprets SQL statements as they come in and responds appropriately. The database manager often has its own proprietary command interface, or it may support still other command interfaces like Xbase.

Little Action Here, Big Action There

That's how most people envision client-server database management operating, and that's the way it has usually been done, up until now. The client application has generally been some simple user-interface front end without a great deal of power or intelligence; hardly more than a smart terminal.

You can take it further than that. This past November, Microsoft introduced its first home-grown database manager at Comdex/Fall, with an interesting feature: In addition to being a fully relational database manager with its own internal command language (a dialect of Basic), it can also act as a SQL front end. I've drawn this one out in Figure 2.

Access can send SQL commands to a remote database manager on a big system, and bring home a subset of the big system's database as one or more query results. The Access user can then work with the local tables (retrieved through SQL commands) by using Access's own language and macros.

This all resolves to the opportunity to do all pertinent data management on the desktop, while leaving the big system to do whatever data management must be done on all company data. If a company has local offices in 14 states, each state office can have its own 486 with a copy of Access to do its local data management at home, close to the people who do the work and need the results. The big machine back at CHQ still contains all the data from all local offices, and can do company-wide queries and reports as needed. Someone (I forget who) coined the term "rightsizing" to cover systems like this.

(I'm not implying, by the way, that Microsoft Access is the first or the only database manager to be able to do this. It's only the first in my experience. There are probably others, and there will be more in the near future.)

Learning SQL

I think it's a good idea to learn SQL even if you aren't in a shop that has to bridge the gap between the big systems and the important systems. The gap between client and server can be as small as the gap across a function call and in such a situation, using a SQL database manager is pretty much the same thing as linking in a database library like the Paradox Engine or B-Tree Filer. That's pretty much what I've been doing, and the process has been delightful.

I've been using a product called Ocelot2 (whose full name is Ocelot2--the SQL!, including the exclamation point) from Ocelot Computer Services in Edmonton, Alberta. The same box gives you a Windows DLL and a DOS linkable library. It's not what I call cheap ($700.00), but SQL products are generally expensive, and it's less expensive than many I've seen.

One good thing about SQL's being a strong standard is that numerous books have been written on it, nearly all of which are better than Ocelot's somewhat lame documentation. Any sizable bookstore will offer you a number of books on SQL, and most are at least readable. (One to avoid at all costs, however, is SQL Structured Query Language, by Dr. Carolyn Hirsch and Dr. Jack L. Hirsch, published by Windcrest/McGraw-Hill. This has the dubious honor of being one of the worst computer books I've read, and cements my conviction that one should never buy computer books by people who insist on putting "Dr." in front of their names--especially when neither doctorate has anything to do with computer science.) I learned SQL by skimming a few books and then just hacking around interactively with the Ocelot2 SQL back end, through a simple SQL terminal" application provided with the Ocelot2 product. The terminal simply allows you to type in a SQL command and then transfer it to the back-end database manager. Any responses from the database manager are displayed for examination.

It was a lot of fun. The Ocelot2 product is solid and fast, and I do recommend it. The documentation should be rewritten and reprinted by the time you read this.

The Structure of a SQL Command

SQL commands have a relatively simple underlying structure that is awesomely cluttered with qualifiers. Its data-management power is in the qualifiers--but its advantage for learning is that you can shovel away the qualifiers and see the bones of the language in the form of simple (if not necessarily useful) commands.

SQL commands typically begin with a SQL reserved word, followed by a string of qualifiers, and terminated by a semicolon. Unlike Pascal, SQL's semi-colons are terminators, not separators. Each statement must be terminated by a semicolon, regardless of that statement's position in a sequence. Some statements may have other statements embedded within them--hence the "structured" in Structured Query Language. The language is not case sensitive, but standard practice is to place all SQL identifiers and reserved words in upper case.

Listing One (page 126) is a series of SQL CREATE commands that I used to create a database. If you tuned in last month, you'll recall a three-table database of contact names, locations, and phone numbers shown in last month's Figure 3. Listing One is the SQL code it took to create that database through Ocelot2.

The first statement creates the database, which in SQL is a named umbrella covering all of the database's diverse components. This umbrella is called a catalog, and it contains information summarizing the current state of the tables and indexes comprising the database. The CREATE TABLESPACE and CREATE INDEXSPACE commands direct SQL to store the database's various tables in a single file called CONTABLE.TBL, and all its indexes in a single file called CONINDEX.IND. This reduces file clutter somewhat, though it may also reduce performance with larger files.

The CREATE TABLE statements define the individual tables and their component fields. If a table has a primary key, the primary-key field is marked PRIMARY KEY. If a table contains a foreign key (that is, another table's primary key) to link it to its parent's table, that foreign key is marked by the REFERENCES qualifier, followed by the name of the parent table. The REFERENCES qualifier assumes that the name of the primary-key field in the parent table is the same as the name of the foreign-key field in the child table. That is, if a field ConID) references the table ConBase, the field it references in ConBase must also be named ConID.

Most of the field definitions should be self explanatory. Mostly what they do is name a field, give it a type, and then specify how large it is. NOT NULL means that SQL must disallow a record update that leaves a NOT NULL field empty.

I created an index for ConBase to speed queries, but indexes are optional and the database will work well (if slowly) without them.

Asking Questions

Ocelot2 has a nice feature that is evidently nonstandard SQL: It can import a properly structured ASCII comma-delimited text file into a SQL table. This allowed me to suck in a file I had exported from Paradox 3.5 as ASCII, and immediately begin work with a 500-record database. That sure beat typing in beaucoup lines of demo data!

But once you've created a database and gotten data into it somehow, the interesting stuff becomes possible. The SELECT statement is the most-used one in all SQL, and through it you create subset tables from existing tables, according to the qualifiers you place after the SELECT reserved word.

SELECT statements read easily until they get heavily nested. Here's a simple one that selects all records from ConBase with the string "Editor" in the Specialty field:

  SELECT*FROM ConBase   WHERE Specialty='Editor';

The SELECT* clause means "select all fields." You could also have written SELECT FName, LName and gotten only the first-name and last-name fields.

A host of logical qualifiers is available so that you can pin things down any way you want. You could get very choosy, like this:

  SELECT* FROM ConBase   WHERE (Specialty='Editor' OR   Specialty='Writer')   AND (Tag='A');

Bringing Back the Bacon

Once you've successfully executed a query in the server, you've got to get the results table home somehow. When the server executes a query, it retains the result table internally. The server does not automatically squirt the whole result table back over the link to the client. The client has to ask for it and fetch it back over the link, one row at a time.

A SQL cursor is an invisible pointer to one row of a table. When a table is created and a cursor is defined for it, the cursor initially points to the first row in the table. You can use the FETCH command to position the cursor to some row in the table and then retrieve that row, one field at a time. FETCH can position the cursor to the next or the prior row, to the first or last row in the table, or to a row specified by row number -- either the absolute row number or some number relative to the current cursor position.

EXEC SQL FETCH NEXT MyCursor INTO :ln :fn :tg :cl :sp :tx;

Here, the named cursor MyCursor is moved to the next row in the results table, and brings back the row into six host variables named ln, fn, tg, cl, sp, and tx. The host variables are separated by their prefixed colons.

FETCH is only available as an embedded command; that is, a user cannot interactively type a FETCH command from a terminal. This is the reason for the EXEC SQL immediately before FETCH. EXEC SQL indicates that what follows is a SQL statement and not just another host language (Pascal, Basic, Cobol, and the like) statement. Combining SQL statements and host language statements is still an ugly business and could be made a lot easier (as I'll emphasize a little later) by the hostlanguage compiler vendors.

The Boundaries of Standardization

There's a whole lot more to SQL than I've shown on this quick tour. There are additional reserved words for updating and deleting databases, for restructuring them, and for handling issues like concurrent access and security.

On the whole, the SQL standard is remarkably strong, stronger than Xbase (which has lots of loose ends) with only a few, probably necessary exceptions. Embedded SQL (at least through Ocelot2) requires that the source file be precompiled by a host-language specific SQL precompiler, which takes standard SQL statements and translates them into host-language statements that implement the SQL statements in the host language. Other SQL vendors may handle embedded SQL in a different fashion; the standard says little or nothing about the process of creating an embedded SQL application.

And because SQL is platform independent, there are going to be some bumps when you finally have to hook the logical to the physical somehow and store SQL databases in DOS files. Ocelot2's mechanisms make sense to me, but they are not identical to those used by other SQL vendors.

Statements Held in Common

If you're working on a data-management application in Pascal, you're wasting serious time trying to implement the data manager proper from scratch. You might as well smelt your own steel and hammer out a replacement fender in a blacksmith's forge. Auto parts are widely available and don't cost that much, and neither do the multitude of database engines and development tools of various kinds.

The truly ugly question has been turning up more and more often: Do I need to work in Pascal at all? The current generation of high-end database managers are lightning-fast and contain everything you need to create a whole application, and every time I've done it, I've accomplished in days what in Pascal would have taken weeks. I know that a lot of people I've spoken with have been forced by productivity squeezes to abandon traditional languages entirely and work in "database languages" instead.

Over time this could hurt the Pascal industry. One way out has become pretty obvious to me: Borland (and all Pascal language vendors) should think very hard about agreeing on an embedded SQL standard, and incorporating that standard into the language, just as x86 assembly language has now been incorporated into Turbo Pascal through BASM. The data-manager executable itself doesn't necessarily have to be included with the Pascal product, but the full embedded SQL syntax should be understood by the Pascal compiler, and the means by which SQL statements may be passed to the data manager should be standardized as well. Then the programmer could choose from among many third-party, back-end SQL data managers, all of which would respond to identical SQL commands generated from within a Pascal application.

Like it or not, we're going to have to start shaving the time it takes to produce useful things in traditional languages, and one way to do this is to start using standardized parts. SQL is one such standardized part. It should be a lot easier to make use of the SQL standard from Pascal. The SQL people have done pretty much what they can. The ball is now in the Pascal vendors' (and specifically Borland's) court.

Products Mentioned

Ocelot2--the SQL! Ocelot Computer Services Inc. Suite 1104, Royal Trust Tower Edmonton Center Edmonton, AB T5J 2Z2 Canada 403-421-4187 $695.00


Copyright © 1993, Dr. Dobb's Journal