Ken has been developing software--including DBMS projects for mainframe, mini, PC, and client-server systems--for 25 years. Contact him at Resource Group Inc., 2604B El Camino Real, #351, Carlsbad, CA 92008 or on CompuServe at 71301,1306.
If you're a database programmer, acronyms such as "ODBC" and "IDAPI" are likely already part of your vocabulary. Microsoft's Open Database Connectivity (ODBC) API and the Integrated Database API (IDAPI) from Borland, Novell, IBM, and WordPerfect are emerging multi-database technologies that use loadable drivers to provide access to multiple DBMSs and database formats. A third tool, the Q+E Database Library (Q+E Lib) from Pioneer Software, also provides support for multiple DBMSs via a single API.
In a perfect world, programmers using one of these middleware solutions would be able to support different database platforms from a single set of source code. But when it comes to DBMSs, we don't live in an ideal world. This article explores tools which attempt to fill the gap between the real and the ideal. In particular, I'll examine Pioneer Software's Q+E Lib and Microsoft's ODBC 1.0, an extension to Windows. (IDAPI reference materials were not available in time to include in this article.) In the process, I'll present a minimal SQL class library written in Borland C++ that works with APIs for multi-DBMS programming. I'll also provide a Windows utility that uses the class library to identify the structure of tables in different DBMS formats.
Q+E Lib is a library consisting of Windows and OS/2 DLLs that implements gateways or database drivers. The DLLs include drivers that deliver SQL access to DB2, Ingres, Oracle, SQL Server, Sybase, Netware SQL, OS/2 DBM, XDB, SQLBase, Paradox, Btrieve, dBase, Excel, and text data. Although some features are driver dependent, for many purposes you need only one set of source code.
The ODBC architecture is similar to Q+E Lib, so it is no coincidence that Pioneer will supply many of the early drivers when Microsoft releases ODBC. Both Q+E Lib and ODBC provide a standard call interface for application programs because the driver layer addresses the difference in SQL implementations.
Q+E Lib includes dozens of functions for data conversion and functions that return the data type of a column as a Q+E data type or the DBMS's internal data type. It supports transaction processing (where supported by the driver) with functions to begin, commit, and rollback transactions. It includes a qeSetDB function to change the default database (where the DBMS supports multiple databases) and fetch options that fetch the next record (qeFetchNext), values for data types (qeValxxxx), multicolumn data, and data bound to program variables (qeBindCol).
For debugging, it provides functions to log calls to connection and execution functions (qeTraceOn and qeTraceOff). Q+E Lib supports more than one level of error reporting. It is good programming practice to check the status after each Q+E Lib function call by calling qeErr. If it returns an error, call qeErrMsg for error messages or qeDBErr for the error code from the database system.
To successfully link with the Q+E Lib import lib, it is important to avoid C++'s name-mangling by defining the function prototypes with an "extern C" wrapper. The QEAPI.H header illustrates this technique.
The first step in designing a class library is determining the ideal complexity and depth of the class hierarchy. Because SQL entities map easily into C++ classes, some developers prefer to construct a fairly complex hierarchy by creating a C++ object for each SQL object (tables, views, cursors, rows, columns, values, and individual data types). Others prefer less complexity and a higher level of abstraction. As Figure 1 illustrates, I took the minimalist approach to implement the SQL class hierarchy.
dbObject (see Listings One and Two, page 74) is the base class that represents generic object behavior. To assist debugging and memory management, dbObject maintains a count of the number of users of an object. In the near future, operating systems will provide multithreaded, networked, object-oriented, parallel, or multiprocessing operations. We will optimize applications to work with object-memory managers (demand-paged, virtual-memory management on an object basis) and distributed object managers, so reference counts will become increasingly important.
dbDatabase (Listing Three, page 74) is the parent class for database objects such as tables, columns, and related objects. Although some DBMS products define a single table as a database, the generally accepted definition is a set of tables accessed by a DBMS. For example, an engineering application that uses an Oracle database for stress data and a Sybase database for a parts breakdown will have two instances of the dbDatabase class. dbDatabase maintains database status and environment information.
dbConnection (Listings Four and Five, page 74 and 75) encapsulates a connection to a data source. A data source is an object for which there is a driver and path, and optional attributes such as server names, user IDs, and passwords. Connection to a dBase or Paradox data source with Q+E Lib drivers requires only the driver name, whereas the driver for other SQLs may require user IDs and passwords. dbConnection is a child of dbDatabase. Its Connect member creates a connection string (pointed to by a far pointer) and passes it to qeConnect. If the connection is established, qeConnect returns a valid connection handle. The Disconnect member function passes the connection handle when it calls qeDisconnect to terminate the connection.
dbRequest (Listings Six and Seven, page 77 and 78) encapsulates the SQL request. The request represents an individual SQL command or query, including preparation and execution of the statement. It may also include the implementation of cursors (vehicles for tracking positions), although the example doesn't use cursors for Paradox or dBase files. dbRequest is derived from dbConnection. When dbRequest is instantiated, it receives the command, the connection handle, and database option settings. It uses Build Request to process the command and supply a far pointer to ExecuteStatement, which calls qeExecSQL to execute the SQL request. qeExecSQL returns a valid statement handle if the command executes. FindNumberofColumns takes a statement handle and returns a count of the number of columns associated with that statement. TerminateStatement takes a statement handle and calls qeEndSql to terminate the request. AllocateStatement and GetCursorforRequest are used with SQL implementations in which an application preallocates statement and cursor handles. dbOptions and RequestOptions are options for each database and request. A program that accesses several data sources using a variety of drivers must maintain a context because capabilities and options may vary from driver to driver and request to request.
dbColumn (Listings Eight and Nine, page 79) maintains information on columns or fields in relational database tables. In this example, it includes calls to Q+E Lib's functions that manipulate columns. dbColumn is a child of dbRequest; arguments passed to its constructor are the connection handle, database options, statement handle, SQL string, and an integer-column position. Describe calls qeColName to obtain the column name, qeColType for the data type, qeColWidth for the width of the longest value that the column can store, and where appropriate, qeColScale for the column scale. DecodeQEDataType maps the column's integer data type to an equivalent type description.
Finally, dbBlob (Listings Ten and Eleven, page 81) supports binary large objects such as images or sound. I'll examine each of these in the context of Q+E Lib and ODBC.
SQLSTRUC.CPP (Listing Twelve, page 82) is a utility to demonstrate the SQL library. The ODBC API includes function calls that determine the features and capabilities supported by a given database driver. However, Q+E Lib, version 1.x does not include a comparable function set, so the options are handled in the SQLSTRUC application. For simplicity, SQLSTRUC doesn't include Windows GUI classes. Rather, it is designed with a command-line interface that can be linked with Microsoft's QuickWin or Borland's EasyWin library. These libraries are useful for quick porting of command-line utilities that use standard C I/O.
SQLSTRUC sets data-source values and instantiates the data source and options for this example. It prompts for arguments (table, path, and driver) and instantiates a connection to the driver. If there is no error, it creates the query string, instantiates the request, defines request options, builds the request, executes the statement, determines the number of columns, and performs a loop to produce the description of each column. When it finishes the loop, it terminates the statement and connection.
The complete project, including the SQL class library and example program, is available electronically; see "Availability," page 3. To build this project, you need the Q+E Database Library. The program identifies the choice of DBMS by prompting for the name of the database driver. Driver names are a character string such as QEDBF (dBase), QEPDX (Paradox), and QEORA (Oracle); refer to the Q+E documentation for driver dependencies. For example, the Paradox driver requires SHARE and the Paradox Engine DLL. When using make or project files for Windows EXEs that bind to DLLs, you must include an import lib that identifies the entry points in the DLL. If you don't have an import lib, you may create one using EXEHDR or IMPLIB. To run the example, you must supply three arguments: the name of the table whose structure you wish to list, the DOS pathname that identifies the location of database files, and the database driver. Some products such as dBase and Paradox store the structure information in the same directory as the tables, whereas products like Netware SQL often use a separate directory for data-dictionary files.
To implement a fully featured SQL class library or application framework, additional items may be necessary. SQL applications call for a robust error-handling class that processes SQL engine errors (local and remote), driver errors, and internal debugging errors. Your applications may benefit from a custom memory manager that overloads the new and delete operators and uses the error handler to recover when there is insufficient memory to instantiate objects. Multimedia applications call for server classes for images, video, and sound, and many SQL implementations require classes that support block, scrollable, or named cursors as a vehicle for tracking position in a view.
Additional features are associated with security and data integrity issues. Most SQLs implement some form of transaction processing and security, so a class design should consider group privileges, concurrency, and commit/rollback support. Some SQL servers provide logic such as stored procedures or triggers that execute at the server. They are not standard SQL, but powerful extensions desirable for robust library implementations. The final format of the classlibs is another implementation decision. One solution is to supply the libraries as object libraries, but some classes are probably candidates for shared-class DLLs. This method of implementation may require a bit more analysis, but DLLs have definite benefits.
Like other layered products, ODBC consists of several components. The application interacts with the ODBC Driver Manager (ODBC.DLL), which sits at a layer above one or more single- or multiple-tier drivers. Single-tier drivers process both ODBC calls and SQL statements; they sit directly above the data source. Multiple-tier drivers process the ODBC calls but pass the SQL statements to a server for processing. The Driver Manager processes some ODBC calls without driver involvement.
The ODBC Software Development Kit (SDK) includes several components needed to develop ODBC applications in C++. The required files that Microsoft ships include the Driver Manager DLL (ODBC.DLL), its import library (ODBC.
LIB), and the headers for core-level functions (SQL.H) and extended functions (SQLEXT.H). The SDK also includes a framework for a sample driver, a driver test program (GATOR), and a Visual Basic sample application. The SDK documents installation and setup functions available to Windows-based driver-installation programs. When you install an ODBC-compliant engine, the DBMS vendor will ship most of the files necessary to develop and administer ODBC-enabled applications. Microsoft does not currently license the header files for redistribution, so you will need the ODBC SDK.
Most SQL engines that include an ODBC driver provide a Windows installation program that requires little conscious decision making, with the exception of reconciling previously installed software (ODBC executables and DLLs). The SDK documents installation and setup functions. Once ODBC is installed, the ODBC Administrator remains on your Windows desktop. The Administrator is your vehicle for providing path and ID information about your databases. When you run the Administrator, select an installed driver, and click on the Configure button, the program will display driver-dependent setup dialog boxes. To configure the data source, some drivers require minimal information such as a name and description. Other drivers require more extensive information.
The ODBC SDK includes headers for core ODBC functions (SQL.H) and extended ODBC functions (SQLEXT.H). These headers do not follow the C++ compatibility convention used in the headers for Microsoft's I/O library. To avoid the C++ name-mangling problem, you will have to revise SQL.H and SQLEXT.H by including a linkage specifier or wrapper around the ODBC function prototypes. QEAPI.H illustrates the use of the "extern C" linkage specifier.
Programmers that have used other SQL products will find familiar territory in most of the concepts embodied in the ODBC API--fetches, commits, rollbacks, and cursors, for example. Functions unique to ODBC are those that
relate to matching a programming interface to a variety of DBMS engines. One of the principal differences between developing for ODBC and Q+E Lib (or other SQL libraries) is the informational functions included in ODBC to support run-time programmatic decisions. A developer can use these calls to query the Driver Manager to identify features of the driver and DBMS. These include items such as: conformance to ODBC, SAG, and SQL grammars; supported ODBC functions and data types; whether the driver and DBMS support stored procedures and asynchronous processing; and so on.
One of the advantages to the client-server architecture is the ability to execute code (procedures and triggers) at the server. Microsoft's SQL Server, which is a port of Sybase's SQL Server, shows some of that heritage because it includes function calls that support stored procedures. It will also work with Oracle's triggers, but no function calls make triggers accessible by client applications. ODBC adds support for scrollable and named cursors and a function (SQLSpecialColumns) that permits applications to use custom scrollable cursors. Some SQLs include support for row IDs (the best set of columns that uniquely identify a row). The SQLSpecialColumns column type can be used to retrieve this type of information for Oracle, Ingres, SQLBase, and Sybase.
The ODBC specification defines several levels of conformance, with a Core level that corresponds to the SQL Access Group's Call Level Interface (CLI), which consists of 23 functions. There are three connection functions, five preparation functions, two request submission functions, eight retrieval functions, and six termination functions. Level 1 includes core functions plus 15 additional functions. Level 2 includes core and level 1, plus 16 additional functions.
An application can determine driver functionality at run time by using several informational functions. SQLGetTypeInfo, for example, returns information about data types supported by the SQL engine; SQLGetFunctions returns information about ODBC functions implemented by a driver; and SQLGetInfo returns a variety of information that profiles a driver and data source.
The functions that link to data sources provide an illustration of the hierarchy of calls and conformance levels. An application may use a core level 1 or level 2 function to connect to a data source. The minimal implementation (SQLConnect) that a driver must support includes a provision for user and password information. More sophisticated (level 1) drivers that require additional information such as schemas or procedure catalogs will include a SQLDriverConnect function that instructs the driver to display a dialog box to prompt for DBMS-specific information. Level 2 ODBC drivers also support a function call that provides an iterative, browsing method (SQLBrowseConnect) of connecting.
One approach to supporting multiple DBMS APIs is to implement a single class structure with superclasses for generic DBMS objects. The objects specific to an API such as ODBC are derived from the superclasses. The ODBC and Q+E classes that accompany this article reside in separate libraries. The APIs for IDAPI and Q+E 2.0 are not available at the present time, so the final design of a practical class library that will support all three APIs remains on the agenda with the notation, "real soon now."
One of the objectives that a developer must consider when designing a class library for ODBC is whether to provide a thin wrapper around the ODBC C functions or to organize classes at a higher level of abstraction. One of my objectives was to work at a level that leant itself to development for the Windows GUI. For example, some member functions create lists in a format suitable for use with list-box controls, although the actual code for the Windows dialog is not in the class library.
The database class includes multiple instances of a data source--an entity that implies a path to the data and a loadable DBMS driver. It may also include a user ID, password, and related information. The mapping from a data source to an SQL server may be a many-to-one or a one-to-one relationship. ODBC supports multiple connections to multiple data sources, and some drivers are capable of asynchronous operations. Therefore, a multi-DBMS class library must accommodate applications that connect to several data sources at one time, using multiple drivers. Each connection may have a one-to-one or one-to-many relationship with requests or SQL statements. Requests may have a one-to-one or one-to-many relationship with view, tables, rows, and columns.
There are also singular relationships to manage. There is one ODBC environment handle per application, so it fits nicely into an application or environment class. Some data-source variables such as driver versions and DBMS names are static across one or several connections and requests.
Implementing for ODBC requires changes to the minimalist SQL class hierarchy presented earlier. Besides runtime profiling, ODBC differs from Q+E Lib in the allocation of connection handles and an environment handle.
The Q+E Lib connect function (qeConnect) returns a connection handle if the function call is successful. ODBC provides a separate function (SQLAllocateConnection) to allocate the handle. It requires the handle prior to the application's call to one of the connection functions (SQLConnect, SQLBrowseConnect, and SQLDriverConnect). ODBC uses an application environment handle, a concept that has no counterpart in Q+E Lib. The revised SQL class structure is shown in Table 1.
Programmers making the transition from Q+E Lib to ODBC will note several obvious differences. ODBC provides function calls useful in making run-time decisions about the application's DBMS platform. The ODBC connection class includes a member function (ProfileDataSource) that makes a series of calls to SQLGetInfo in order to create a data source and driver profile. The profile includes information such as version numbers, commit and rollback behaviors, whether the driver supports stored procedures, whether all tables and procedures are accessible, and whether it is compliant with ODBC, CLI, and so on.
DSRCINFO (available electronically) is a command-line utility that prompts for a data-source name and then calls ProfileDataSource and SQLGetInfo to create a data-source profile; see "Availability," page 3. SQLGetInfo is also used to identify the types of scalar data (numeric, string, timedate) and conversion functions that the driver supports. DSRCINFO also obtains through SQLGetInfo the ODBC SQL conformance level, returning a 0 for minimum, 1 for core, or 2 for extended grammar. Using DSRCINFO, I found that although White Cross's SQL is one of the few SQLs certified without conformities, the driver returns a 1. Microrim touts R:base as ANSI level 2 (incorporating IBM DB2 enhancements), but the R:base driver returns a 0. It is important to remember that the SQL level is a measure of conformance with the features of ODBC SQL defined in the SDK documentation. Appendix C of the programmer's manual includes the ODBC SQL Grammar matrix.
Testing for this article included a mix of drivers, in part to demonstrate the scalability of the technology. My tests used the SDK test driver and the released ODBC drivers. Developers of drivers for large-scale servers (Oracle, rdb, Teradata, and White Cross) conducted tests for me. The information from SQLGetInfo is subject to change between the time of this writing and the release of these drivers. Profiles for the dBase driver, Quadbase-SQL, R:base SQL, Watcom SQL, NCR's Teradata, White Cross 9000, Oracle, and DEC's rdb are all available electronically. Finally, the complete ODBC SDK is available free of charge from Microsoft.
SQL Development Tools
One of the most significant trends in DBMS architecture is the emergence of client/server systems that separate the database engine (back end) and the user interface (front end) software. The development of Microsoft's ODBC emphasizes that separation because developers will soon be writing Windows front ends that will work with a variety of back-end DBMS products. One of the benefits of the ODBC architecture is scalability. To demonstrate scalability for this article, I ran test programs with PC-based SQL engines and "super" servers (Teradata and White Cross).
In recent months, several companies have released SQL database engines that include drivers for ODBC. By installing one of these engines, it is possible to begin development of desktop applications that may eventually connect to mainframe and server-based SQL products that will be shipping drivers in 1993.
One of the advantages of these products is that they permit a developer to write software for laptops that will work with large mainframe data bases. In addition to support for the ODBC API, they include other features that differentiate the products. These unique features include BLOB support, Windows and DOS ODBC libraries, read-only schemas for CD-ROM, PenPoint support, dynamic data exchange (DDE) support, and statistical feedback for tuning and optimizing queries.
Microrim's R:Base Engine supports level 2 SQL for Windows and DOS development. Developers may write DOS SQL applications using the same API they will use for ODBC and Windows applications, although the DOS support is in the form of object libraries that are compatible only with Microsoft C. Microrim ships the product with sample code for C and Visual Basic.
Quadbase-SQL 2.0 includes support for a call-level interface, ANSI Embedded SQL (ESQL), a language-independent embedded SQL, and support for BLOBs (up to 2 Gigabytes). It includes DLLs for both Quadbase's native API and ODBC and an ODBC DLL and custom controls for Visual Basic. It also includes classes for Actor; sample code for Toolbook, Pascal, and C; and DOS and Windows query utilities.
Watcom is shipping SQL Engines for DOS, QNX, Windows, and PenPoint. The Windows product includes 16- and 32-bit engines, embedded SQL and a level 2 implementation of ODBC. Watcom's utilities include statistical feedback that assists in tuning the query optimizer and libraries that implement a Windows DDE server.
Developers looking for high-performance servers will find that ODBC is available at that end of the performance spectrum. DEC's 64-bit Alpha chip is one of the new RISC chips that has gained favor with DBMS server companies. Oracle, Ingres, DEC's own rdb, and other powerful servers will be available on Alpha systems that will deliver 150 MIPS or more. NCR's Teradata is a high-performance, fault-tolerant parallel-processing system capable of managing terabytes of information. A Teradata box can communicate with MVS, VM, UNIX, VMS, and other systems.
White Cross Systems of England markets a new transputer-based server built with the latest in RAID and fiber-optic technologies. The 120 MIPS entry-level system will reputedly query 2.4 million rows per second. It provides ODBC and X/Open call interfaces to Windows and UNIX clients, respectively.
Tools for developing the client side of client/server applications include query products, database products with client/server capability, and tools that enhance the capability of programming languages. At the time of this article, the query toolmakers are not shipping ODBC-enabled products, but that is likely to change as demand increases.
Microsoft's Visual Basic has become legendary for its ease of prototyping. The Professional Edition (3.0) includes support for ODBC and MAPI, Microsoft's messaging interface. The ODBC SDK includes a Visual Basic 1.0 demo program.
Crystal Services has expanded the database options available to users of its Crystal Reports product. Version 2.0 includes connectivity to various SQL databases in addition to Paradox, Btrieve, and FoxPro databases. The report-engine DLL works with C++, C, VB, TPW, and ObjectVision. (Crystal Reports is also bundled with Visual Basic 3.0.)
Although Visual Basic receives a lot of press as a premier prototyping tool, there are also very powerful tools for C and C++ developers. Borland's Resource Workshop is a high-end editor of Windows resource files that includes Motif-like custom controls. ProtoView Development Corporation's suite includes ProtoGen (application generator), ProtoView (screen manager), and Data Table (a spreadsheet custom control).
Microsoft's Access acts as a DBMS in its own right or as a client in client/ server application using ODBC. Access 1.0 shipped with an ODBC driver for SQL Server and Microsoft no-ted that it conducted no tests using Access with other drivers. In my tests of SQL engines for this article, the Access links to other drivers were tenuous. Simple import operations of simple data types were more likely to succeed than export and update operations. (The Access engine is also shipped with Visual Basic 3.0.)
--K.N.
Copyright © 1993, Dr. Dobb's JournalFigure 1: SQL class hierarchy.
Table 1: Revised SQL Class Hierarchy.
Function Description
dbObject Base class that represents generic object behavior.
dbEnvironment Single-instance, application class.
dbDatabase Parent class for database objects such as tables,
columns, related objects.
dbDataSource Encapsulates the path and identifier information.
dbConnection Encapsulates a connection to a data source.
dbRequest Encapsulates the SQL request.
dbColumn Maintains information on columns in relational
database tables.
dbBlob Binary large object (BLOB) such as images or sound.
[LISTING ONE]
///////////////////////////////////////////////////
// FILE NAME: dbObject.h TITLE: database class
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
///////////////////////////////////////////////////
#ifndef __DBOBJECT_H
#define __DBOBJECT_H
class dbObject
{
int ReferenceTotal;
protected:
public:
dbObject();
virtual ~dbObject();
virtual void IncrementRefs();
virtual int DecrementRefs();
};
#endif
[LISTING TWO]
///////////////////////////////////////////////////
// FILE NAME: dbObject.cpp TITLE: base class
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
///////////////////////////////////////////////////
// SYNOPSIS:
// implementation of dbObject class
///////////////////////////////////////////////////
#undef DEBUG
#undef DEBUG1
#include <windows.h>
#include <string.h>
#include "sql.h"
#include "sqlext.h"
#include "sqldefs.h"
#include "dbobject.h"
////////////////////////////////////////////////////
// FUNCTION NAME: dbObject
// SYNOPSIS:
// define dbObject, SQL base class constructor
////////////////////////////////////////////////////
dbObject :: dbObject()
{
ReferenceTotal = 1; // referenced by self
}
///////////////////////////////////////////////////
// FUNCTION NAME: ~dbObject
// define dbObject, SQL data base class destructor
///////////////////////////////////////////////////
dbObject :: ~dbObject()
{
// this is a point where error handler should be
// invoked if the reference total > 1 or < 0
// systemError();
}
////////////////////////////////////////////////////////////////////////
// FUNCTION NAME: IncrementRefs -- increment object reference total
////////////////////////////////////////////////////////////////////////
void dbObject :: IncrementRefs()
{
ReferenceTotal++;
}
//////////////////////////////////////////////////////////////////////////
// FUNCTION NAME: IncrementRefs -- increment object reference total
//////////////////////////////////////////////////////////////////////////
int dbObject :: DecrementRefs()
{
// if the reference total < 0
// {
// systemError();
// }
return(--ReferenceTotal);
}
[LISTING THREE]
///////////////////////////////////////////////////
// FILE NAME: database.cpp TITLE: data base class
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
// SYNOPSIS: implementation of database class
///////////////////////////////////////////////////
#undef DEBUG
#undef DEBUG1
#include <windows.h>
#include <string.h>
#include "sql.h"
#include "sqlext.h"
#include "gendefs.h"
#include "sqldefs.h"
#include "dbobject.h"
#include "sqldb.h"
#ifndef __RGSTRING_H
#include "rgstring.h"
#endif
HDBC dbDataBase::ODBCLinkHandle[]
= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int dbDataBase::nDefinedSources = 0;
int dbDataBase::ActiveLinks = 0;
char dbDataBase::DataSourceList[] = "None";
UCHAR dbDataBase::ODBCLinkList[] = "None";
/* standard return code for calls */
extern char ErrStat;
/* large / huge model */
extern char MemModel;
////////////////////////////////////////////////////
// FUNCTION NAME: dbDataBase
// SYNOPSIS: dbDataBase class constructor
////////////////////////////////////////////////////
dbDataBase::dbDataBase(HENV AppEnv)
: dbObject()
{
// save application environment handle
henv = AppEnv;
// initialize status and error info
StatusReturned = SQL_SUCCESS;
InitODBCErrorInfo();
}
///////////////////////////////////////////////////////////
// FUNCTION NAME: ~DataBase -- DataBase class destructor
//////////////////////////////////////////////////////////
dbDataBase :: ~dbDataBase()
{
}
//////////////////////////////////////////////////////////////////////////
// FUNCTION NAME: InitODBCErrorInfo -- initialize ODBC error information
//////////////////////////////////////////////////////////////////////////
void dbDataBase::InitODBCErrorInfo()
{
err.ErrStatus = 0;
err.ErrorMsgLength = 0;
err.ErrorMsgMax = 0;
err.NativeError = 0;
memset(err.szSQLState,'\x0',
sizeof(err.szSQLState));
memset(err.ErrorMsg,'\x0',
sizeof(err.ErrorMsg));
}
/***************************************************
* FUNCTION NAME: MatchLinkName
* SYNOPSIS: scan the active link list (ODBCLinkList) to find
* the link that matches the selected link name
* (from disconnect dialog box)
*****************************************************/
int dbDataBase::MatchLinkName(UCHAR *LinkToDrop)
{
int tdx; /* link index */
int toff; /* link offset */
UCHAR NameToTest[LINK_NAME_LEN+1];
/* string position to begin search */
unsigned short StartPos=0;
/* position found: result of the search */
unsigned short SearchResult;
for (tdx=0;tdx < ActiveLinks;tdx++)
{
toff = tdx * LINK_NAME_LEN;
memset(NameToTest,'\x0',LINK_NAME_LEN+1);
substr_n((char *)ODBCLinkList,
(char *)NameToTest,
toff, LINK_NAME_LEN ) ;
SearchResult =
left_srch((char *)NameToTest,
(char *)LinkToDrop, StartPos);
if (ErrStat == SUCCESS)
{
return tdx;
}
}
return OOPS;
}
[LISTING FOUR]
////////////////////////////////////////////////////
// FILE NAME: connect.h TITLE: SQL connection
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
////////////////////////////////////////////////////
#ifndef __CONNECT_H
#define __CONNECT_H
#ifndef __SQLDB_H
#include "sqldb.h"
#endif
#ifndef __DATASOUR_H
#include "datasour.h"
#endif
#ifndef __DBPROFIL_H
#include "dbprofil.h"
#endif
#ifndef __ODERROR_H
#include "oderror.h"
#endif
#ifndef __DSINFO_H
#include "DSInfo.h" /* data source information */
#endif
/* Connection is a child of data source */
class dbConnection : public dbDataSource
{
protected:
HENV henv;
POINTER cstr;
public:
RETCODE Status;
RETCODE ErrStatus;
HDBC hdbc;
UCHAR far *conec;
static UCHAR ConnectionString[CONECLEN];
static UCHAR szConnStrOut[CONECLEN];
/* the next two variables are used for situations */
/* where a connection's values are subsets of a */
/* data source's values */
SDWORD ConnTableCount; /* number of tables for this connection */
char ConnTableList[DS_TABLE_LIST_LEN];
dbConnection(HENV);
virtual ~dbConnection();
HDBC AllocateConnection(void);
POINTER BuildBrowseConnectString(char *);
virtual POINTER BuildConnectString(char *);
RETCODE BrowseConnect();
virtual RETCODE Connect(UCHAR *, UCHAR *);
virtual int Disconnect(UCHAR *);
RETCODE DriverConnect(UCHAR *);
void InitConnData(void);
void dbConnection::GetErrorInfo(void);
RETCODE PASCAL ProfileDataSource(DataSourceInfo *);
};
#endif
[LISTING FIVE]
///////////////////////////////////////////////////////////////////
// FILE NAME: connect.cpp TITLE: database connection
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
///////////////////////////////////////////////////////////////////
// SYNOPSIS: implementation of SQL connection class
///////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <dos.h>
#include <malloc.h>
#include "sql.h"
#include "sqlext.h"
#include "sqldefs.h"
#include "gendefs.h"
#include "sqldb.h"
#include "dboption.h"
#include "oderror.h"
#ifndef __DATASOUR_H
#include "datasour.h"
#endif
#ifndef __DSINFO_H
#include "DSInfo.h" /* data source information */
#endif
#include "connect.h"
UCHAR dbConnection::ConnectionString[]="None";
UCHAR dbConnection::szConnStrOut[]="";
/* constructor for dbConnection class */
dbConnection::dbConnection(HENV AppEnv)
: dbDataSource(AppEnv)
{
henv = AppEnv; /* save environment handle */
InitODBCErrorInfo();
Status = SQL_SUCCESS;
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: ~dbConnection
// SYNOPSIS: destructor for dbConnection
///////////////////////////////////////////////////////////////////
dbConnection::~dbConnection()
{
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: AllocateConnection
// SYNOPSIS: allocates a connection handle
///////////////////////////////////////////////////////////////////
HDBC dbConnection::AllocateConnection()
{
/* get connection handle */
Status = SQLAllocConnect(henv, &hdbc);
if (Status != SQL_SUCCESS)
{
return NULL;
};
return hdbc;
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: Connect
// SYNOPSIS: connect to SQL data source
///////////////////////////////////////////////////////////////////
RETCODE dbConnection::Connect(UCHAR *Userid, UCHAR *Password)
{
SWORD cbConnStrOut;
memset(ConnectionString,'\x0',sizeof(ConnectionString));
memset(szConnStrOut,'\x0',sizeof(szConnStrOut));
strcpy((char *)ConnectionString,"DSN=");
strncat((char *)ConnectionString,(char *)TrimmedDSName,
strlen((char *)TrimmedDSName));
/* ODBC driver connect */
Status = SQLDriverConnect (hdbc,
NULL,
ConnectionString,
SQL_NTS,
szConnStrOut,
CONECLEN,
&cbConnStrOut,
SQL_DRIVER_COMPLETE);
if (Status != SQL_SUCCESS)
{
/* ODBC connect */
Status = SQLConnect (hdbc,
DSName,
SQL_NTS,
Userid,
SQL_NTS,
Password,
SQL_NTS);
if (Status != SQL_SUCCESS)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
}
}
ODBCLinkHandle[ActiveLinks] = hdbc; /* add handle to active list */
if (ActiveLinks < 1)
{
strncpy((char *)ODBCLinkList,(char *)DSName,
SQL_MAX_DSN_LENGTH);
}
else
{
strncat((char *)ODBCLinkList,(char *)DSName,sizeof(DSName));
}
ActiveLinks++;
return Status;
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: BuildConnectString
// SYNOPSIS: build connection string for ODBC driver
// return far pointer to connection string
///////////////////////////////////////////////////////////////////
POINTER dbConnection::BuildConnectString(char *str)
{
UCHAR far *conec;
conec = (UCHAR far *) malloc(CONECLEN);
movedata(FP_SEG(str), FP_OFF(str),
FP_SEG(conec), FP_OFF(conec),
1+strlen(str));
return conec;
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: Disconnect
// SYNOPSIS: terminate the SQL connection
///////////////////////////////////////////////////////////////////
int dbConnection::Disconnect(UCHAR *LinkToDrop)
{
int i;
int j;
int nLink; /* index to hdbc to drop */
int offset;
HDBC ConnHandle;
nLink = MatchLinkName(LinkToDrop);
if (nLink < 0)
{
return OOPS;
}
ConnHandle = ODBCLinkHandle[nLink];
/* ODBC disconnect */
Status = SQLDisconnect(ConnHandle);
if (Status)
return Status;
/* compress the list of active links and active handles */
for (i=nLink; i < ActiveLinks;i++)
{
ODBCLinkHandle[i] = ODBCLinkHandle[i+1];
}
offset = nLink * LINK_NAME_LEN;
j = offset;
while ( ODBCLinkList[j+LINK_NAME_LEN] != '\0')
{
ODBCLinkList[j] = ODBCLinkList[j+LINK_NAME_LEN];
j++;
}
ODBCLinkList[j] = '\0';
--ActiveLinks;
/* free conn handle */
Status = SQLFreeConnect(ConnHandle);
if (Status)
return Status;
return SQL_SUCCESS;
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: ProfileDataSource
// SYNOPSIS: information about driver and data source
///////////////////////////////////////////////////////////////////
RETCODE PASCAL dbConnection::ProfileDataSource( DataSourceInfo *inf )
{
/* implementation of error handling is left to the user, since */
/* the user interface may vary. Using QuickWin or EasyWin, you can */
/* use printf statements. If you are writing a typical Windows app, */
/* you can use MessageBox or BWCCMessageBox to display errors */
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_DRIVER_NAME,
&inf->DriverName,
sizeof(inf->DriverName),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ACTIVE_STATEMENTS,
(PTR)&inf->ActiveStatements,
sizeof(inf->ActiveStatements),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ACTIVE_CONNECTIONS,
(PTR)&inf->ActiveConnections,
sizeof(inf->ActiveConnections),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_DRIVER_VER,
&inf->DriverVersion,
sizeof(inf->DriverVersion),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_SERVER_NAME,
&inf->ServerName,
sizeof(inf->ServerName),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_USER_NAME,
&inf->UserName,
sizeof(inf->UserName),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ODBC_API_CONFORMANCE,
(PTR)&inf->ODBC_API_Level,
sizeof(inf->ODBC_API_Level),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ODBC_SAG_CLI_CONFORMANCE,
(PTR)&inf->ODBC_SAG_Level,
sizeof(inf->ODBC_SAG_Level),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ODBC_SQL_CONFORMANCE,
(PTR)&inf->ODBC_SQL_Level,
sizeof(inf->ODBC_SQL_Level),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_DATABASE_NAME,
&inf->DatabaseName,
sizeof(inf->DatabaseName),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_DBMS_NAME,
&inf->DBMSName,
sizeof(inf->DBMSName),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_DBMS_VER,
&inf->DBMSVersion,
sizeof(inf->DBMSVersion),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
/* IEF ? */
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ODBC_SQL_OPT_IEF,
&inf->IEFSupport,
sizeof(inf->IEFSupport),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
/* Support Procedures ? */
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_PROCEDURES,
&inf->Procedures,
sizeof(inf->Procedures),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
/* detect changes in rows between fetches ? */
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ROW_UPDATES,
&inf->RowUpdates,
sizeof(inf->RowUpdates),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
/* all tables accessible */
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ACCESSIBLE_TABLES,
&inf->AccessibleTables,
sizeof(inf->AccessibleTables),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
if(inf->AccessibleProcedures[0] == 'Y')
{
/* all procedures accessible */
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_ACCESSIBLE_PROCEDURES,
&inf->AccessibleProcedures,
sizeof(inf->AccessibleProcedures),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_CONCAT_NULL_BEHAVIOR,
(PTR)&inf->ConcatNullBehavior,
sizeof(inf->ConcatNullBehavior),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_CURSOR_COMMIT_BEHAVIOR,
(PTR)&inf->CursorCommitBehavior,
sizeof(inf->CursorCommitBehavior),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_CURSOR_ROLLBACK_BEHAVIOR,
(PTR)&inf->CursorRollbackBehavior,
sizeof(inf->CursorRollbackBehavior),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_DATA_SOURCE_READ_ONLY,
&inf->DSReadOnly,
sizeof(inf->DSReadOnly),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MAX_COLUMN_NAME_LEN,
(PTR)&inf->MaxColNameLen,
sizeof(inf->MaxColNameLen),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MAX_CURSOR_NAME_LEN,
(PTR)&inf->MaxCursorNameLen,
sizeof(inf->MaxCursorNameLen),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MAX_OWNER_NAME_LEN,
(PTR)&inf->MaxOwnerNameLen,
sizeof(inf->MaxOwnerNameLen),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MAX_PROCEDURE_NAME_LEN,
(PTR)&inf->MaxProcedureNameLen,
sizeof(inf->MaxProcedureNameLen),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MAX_QUALIFIER_NAME_LEN,
(PTR)&inf->MaxQualifierNameLen,
sizeof(inf->MaxQualifierNameLen),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MAX_TABLE_NAME_LEN,
(PTR)&inf->MaxTableNameLen,
sizeof(inf->MaxTableNameLen),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MULT_RESULT_SETS,
&inf->MultipleResultSets,
sizeof(inf->MultipleResultSets),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_MULTIPLE_ACTIVE_TXN,
&inf->MultipleActiveTransactions,
sizeof(inf->MultipleActiveTransactions),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_OUTER_JOINS,
&inf->OuterJoins,
sizeof(inf->OuterJoins),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_OWNER_TERM,
&inf->OwnerTerm,
sizeof(inf->OwnerTerm),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_PROCEDURE_TERM,
&inf->ProcTerm,
sizeof(inf->ProcTerm),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_QUALIFIER_TERM,
&inf->QualifierTerm,
sizeof(inf->QualifierTerm),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_EXPRESSIONS_IN_ORDERBY,
&inf->OrderBy,
sizeof(inf->OrderBy),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
Status = SQL_SUCCESS;
Status = SQLGetInfo(hdbc,
SQL_TXN_CAPABLE,
(PTR)&inf->TransCapable,
sizeof(inf->TransCapable),
NULL);
if (Status != SQL_SUCCESS)
{
GetErrorInfo();
}
if (Status != SQL_SUCCESS)
return Status;
return(SQL_SUCCESS);
}
///////////////////////////////////////////////////////////////////
// FUNCTION NAME: GetErrorInfo
// SYNOPSIS: check SQLError info
///////////////////////////////////////////////////////////////////
void dbConnection::GetErrorInfo()
{
/* implementation of error handling is left to the user, since */
/* the user interface may vary. Using QuickWin or EasyWin, you can */
/* use printf statements. If you are writing a typical Windows app, */
/* you can use MessageBox or BWCCMessageBox to display errors */
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
}
///////////////////////////////////////////////////
// FUNCTION NAME: InitConnData
// SYNOPSIS: initialize conn data
///////////////////////////////////////////////////
void dbConnection::InitConnData()
{
Status = 0;
ErrStatus = 0;
cstr = NULL;
ConnTableCount = 0;
memset(ConnectionString,'\x0',sizeof(ConnectionString));
memset(ConnTableList,'\x0',sizeof(ConnTableList));
}
[LISTING SIX]
///////////////////////////////////////////////////
// FILE NAME: request.h TITLE: SQL request
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
///////////////////////////////////////////////////
#ifndef __REQUEST_H
#define __REQUEST_H
#ifndef __CONNECT_H
#include "connect.h"
#endif
#ifndef __DBOPTION_H
#include "dboption.h"
#endif
#ifndef __RQOPTION_H
#include "rqoption.h"
#endif
#ifndef __TABLESET_H
#include "tableset.h"
#endif
// dbRequest is a child of dbConnection
class dbRequest : public dbConnection {
protected:
UCHAR Statement[MAXSQL];
UCHAR *stmt;
RETCODE Status;
// exceeds max statement length for d.b. driver
BOOL ExceedsMax;
HENV henv;
HSTMT hAStmt;
CURNAME ACursor; /* for named cursors */
UCHAR CursorName[CURSOR_NAME_LEN];
public:
UCHAR far *stptr;
HSTMT hstmt;
DataBaseOptions *dbopt; // options for this database
RequestOptions OptionInfo; // options for this request
dbRequest(HENV, HDBC, DataBaseOptions *, UCHAR *);
virtual ~dbRequest();
virtual HSTMT AllocateStatement(HDBC);
virtual POINTER BuildRequest(BOOL);
virtual RETCODE ExecuteStatement(POINTER);
virtual SWORD FindNumberOfColumns(HSTMT);
virtual CURNAME GetCursorforRequest(HDBC);
SDWORD GetTableList(char *);
void InitTableResultSet(TableResultSet *);
virtual int TerminateStatement(HSTMT);
};
#endif
[LISTING SEVEN]
/////////////////////////////////////////////////////////////////////
// FILE NAME: request.cpp TITLE: SQL request
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
/////////////////////////////////////////////////////////////////////
// SYNOPSIS: SQL request class
/////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "sql.h"
#include "sqlext.h"
#include "gendefs.h"
#include "sqldefs.h"
#include "sqldb.h"
#include "connect.h"
#include "dboption.h" /* database options */
#ifndef __ODERROR_H
#include "oderror.h"
#endif
#ifndef __RGSTRING_H
#include "rgstring.h"
#endif
#include "request.h"
extern char ErrStat; /* return code for string calls */
extern char MemModel; /* large / huge model */
/* Constructor for the 'Request' class: */
dbRequest :: dbRequest( HENV envhandle,
HDBC connhandle,
DataBaseOptions *opt,
UCHAR *SQLstring )
: dbConnection(envhandle)
{
// save SQL string and options
dbopt = opt;
strcpy((char *)Statement,(char *)SQLstring);
stmt = &Statement[0];
// set request environment handle
henv = envhandle;
// set request connection handle
hdbc = connhandle;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: ~dbRequest
// SYNOPSIS: destructor
/////////////////////////////////////////////////////////////////////
dbRequest::~dbRequest()
{
free(stptr);
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: BuildRequest
// SYNOPSIS: prepare an SQL request
/////////////////////////////////////////////////////////////////////
POINTER dbRequest::BuildRequest( BOOL ExceedsMax )
{
if (dbopt->AllocStmtHandle)
{
hAStmt = AllocateStatement(hdbc);
}
if (OptionInfo.UseCursor)
{
ACursor = GetCursorforRequest(hdbc);
}
stptr = (UCHAR far *) malloc(MAXSQL);
movedata(FP_SEG(stmt), FP_OFF(stmt),
FP_SEG(stptr), FP_OFF(stptr),
1+strlen((char *)stmt));
return stptr;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: ExecuteStatement
// SYNOPSIS: executes a direct or prepared request
/////////////////////////////////////////////////////////////////////
RETCODE dbRequest::ExecuteStatement( POINTER stptr )
{
Status = SQLExecDirect (hAStmt,
stptr,
SQL_NTS );
if (Status != SQL_SUCCESS)
{
return Status;
};
return SQL_SUCCESS;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: FindNumberOfColumns
// SYNOPSIS: get the number of columns in the request
/////////////////////////////////////////////////////////////////////
SWORD dbRequest::FindNumberOfColumns( HSTMT hstmt )
{
SWORD n;
Status = SQLNumResultCols (hAStmt, &n);
if (Status != SQL_SUCCESS)
{
return Status;
};
return n;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: GetTableList
// SYNOPSIS: get the tables for this SQL data source
/////////////////////////////////////////////////////////////////////
SDWORD dbRequest::GetTableList(char *TableList)
{
SDWORD nTables=0;
SDWORD QualifierLength;
SDWORD OwnerLength;
SDWORD NameLength;
SDWORD TypeLength;
SDWORD RemarksLength;
UCHAR FAR *szTableQualifier=NULL;
SWORD cbTableQualifier=0;
UCHAR FAR *szTableOwner=NULL;
SWORD cbTableOwner=0;
UCHAR FAR *szTableName=NULL;
SWORD cbTableName=0;
UCHAR FAR *szTableType=NULL;
SWORD cbTableType=0;
TableResultSet *rset;
char NameString[TABLE_NAME_LEN+1];
rset = new(TableResultSet);
if(!rset)
return NO_MEMORY;
InitTableResultSet(rset);
Status = SQL_SUCCESS;
ErrStatus = 0;
Status = SQLTables( hstmt,
szTableQualifier,
cbTableQualifier,
szTableOwner,
cbTableOwner,
szTableName,
cbTableName,
szTableType,
cbTableType);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
Status = SQL_SUCCESS;
Status=SQLBindCol(hstmt, 1, SQL_C_CHAR,
&rset->TABLE_QUALIFIER,
sizeof(rset->TABLE_QUALIFIER), &QualifierLength);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
Status = SQL_SUCCESS;
ErrStatus = 0;
Status=SQLBindCol(hstmt, 2, SQL_C_CHAR,
&rset->TABLE_OWNER,
sizeof(rset->TABLE_OWNER), &OwnerLength);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
Status = SQL_SUCCESS;
ErrStatus = 0;
Status=SQLBindCol(hstmt, 3, SQL_C_CHAR,
&rset->TABLE_NAME,
sizeof(rset->TABLE_NAME), &NameLength);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
Status = SQL_SUCCESS;
ErrStatus = 0;
Status=SQLBindCol(hstmt, 4, SQL_C_CHAR,
&rset->TABLE_TYPE,
sizeof(rset->TABLE_TYPE), &TypeLength);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
Status = SQL_SUCCESS;
ErrStatus = 0;
Status=SQLBindCol(hstmt, 5, SQL_C_CHAR,
&rset->REMARKS,
sizeof(rset->REMARKS), &RemarksLength);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
/* Loop to fetch data for SQLTables */
while (Status != SQL_NO_DATA_FOUND)
{
InitTableResultSet(rset);
Status = SQL_SUCCESS;
ErrStatus = 0;
Status = SQLFetch(hstmt);
if(Status == SQL_ERROR)
{
err.ErrorMsgMax = SQL_MAX_MESSAGE_LENGTH - 1;
ErrStatus = SQLError(henv,
hdbc,
SQL_NULL_HSTMT,
&err.szSQLState[0],
&err.NativeError,
&err.ErrorMsg[0],
err.ErrorMsgMax,
&err.ErrorMsgLength);
if (ErrStatus < 1)
return Status;
}
strcpy(NameString,(char *)rset->TABLE_NAME);
right_fill(NameString,SPACE,TABLE_NAME_LEN);
strncat(TableList,NameString,TABLE_NAME_LEN);
nTables++;
}
return nTables;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: InitTableResultSet
// SYNOPSIS: initialize result set for GetTables
/////////////////////////////////////////////////////////////////////
void dbRequest::InitTableResultSet(TableResultSet *rs)
{
memset(rs->TABLE_QUALIFIER,'\x0',sizeof(rs->TABLE_QUALIFIER));
memset(rs->TABLE_OWNER,'\x0',sizeof(rs->TABLE_OWNER));
memset(rs->TABLE_NAME,'\x0',sizeof(rs->TABLE_NAME));
memset(rs->TABLE_TYPE,'\x0',sizeof(rs->TABLE_TYPE));
memset(rs->REMARKS,'\x0',sizeof(rs->REMARKS));
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: TerminateStatement
// SYNOPSIS: terminate the statement
/////////////////////////////////////////////////////////////////////
RETCODE dbRequest::TerminateStatement(HSTMT hstmt)
{
Status = SQLFreeStmt(hstmt, SQL_DROP);
if (Status)
return Status;
else
return SQL_SUCCESS;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: AllocateStatement
// SYNOPSIS: allocates a statement handle
/////////////////////////////////////////////////////////////////////
HSTMT dbRequest::AllocateStatement(HDBC hdbc)
{
Status = SQLAllocStmt(hdbc, &hstmt);
if (Status != SQL_SUCCESS)
{
return NULL;
};
return hstmt;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: GetCursorforRequest
// SYNOPSIS: gets a cursor for this request
/////////////////////////////////////////////////////////////////////
CURNAME dbRequest::GetCursorforRequest(HDBC hdbc)
{
return NULL;
}
[LISTING EIGHT]
/////////////////////////////////////////////////////////////////////
// FILE NAME: column.h TITLE: SQL column class
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
/////////////////////////////////////////////////////////////////////
#ifndef __COLUMN_H
#define __COLUMN_H
#ifndef __DBOPTION_H
#include "dboption.h"
#endif
#ifndef __REQUEST_H
#include "request.h"
#endif
#ifndef __COLDESC_H
#include "coldesc.h"
#endif
/* 'dbColumn' is a child of 'dbRequest' */
class dbColumn : public dbRequest {
protected:
HSTMT hstmt;
public:
RETCODE Status;
/* column info for this request */
ColumnDesc ColumnInfo[MAXFIELD];
dbColumn(HENV, HDBC, DataBaseOptions *, HSTMT, UCHAR *, UWORD);
virtual ~dbColumn();
virtual int Describe(UWORD, ColumnDesc *);
virtual char * DecodeODBCDataType(SWORD);
};
#endif
[LISTING NINE]
/////////////////////////////////////////////////////////////////////
// FILE NAME: column.cpp TITLE: database column
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
//
/////////////////////////////////////////////////////////////////////
// SYNOPSIS: implementation of SQL column class
/////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "sql.h"
#include "sqlext.h"
#include "sqldefs.h"
#include "sqldb.h"
#include "connect.h"
#include "dboption.h"
#include "rqoption.h"
#include "request.h"
#include "column.h"
/* Constructor for the 'dbColumn' class: */
dbColumn :: dbColumn( HENV envhandle,
HDBC connhandle,
DataBaseOptions *opt,
HSTMT hStatement,
UCHAR *SQLstring,
UWORD ColumnPosition )
: dbRequest( envhandle, connhandle, opt, SQLstring)
{
hstmt = hStatement;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: ~dbColumn
// SYNOPSIS: destructor for dbColumn
/////////////////////////////////////////////////////////////////////
dbColumn::~dbColumn()
{
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: Describe
// SYNOPSIS: get a column description
/////////////////////////////////////////////////////////////////////
RETCODE dbColumn::Describe( UWORD ColumnPosition, ColumnDesc *fi)
{
UCHAR FAR *col;
SWORD NameBufLen=NAMELENGTH;
SWORD ncount;
/* get the column name, type, width, etc. */
Status = SQLDescribeCol ( hstmt,
ColumnPosition,
&fi->Name[0],
NameBufLen,
&ncount,
&fi->DataType,
&fi->Precision,
&fi->Scale,
&fi->Nullable);
if (Status != SQL_SUCCESS)
{
return Status;
}
return SQL_SUCCESS;
}
/////////////////////////////////////////////////////////////////////
// FUNCTION NAME: DecodeODBCDataType
// SYNOPSIS: return description of data type
/////////////////////////////////////////////////////////////////////
char *dbColumn::DecodeODBCDataType(SWORD DataType)
{
static char Type[20];
strcpy((char *)Type,"Unknown");
if (DataType == SQL_CHAR)
strcpy((char *)Type,"character");
if (DataType == SQL_VARCHAR)
strcpy((char *)Type,"variable len char");
if (DataType == SQL_LONGVARCHAR)
strcpy((char *)Type,"long variable char");
if (DataType == SQL_DECIMAL)
strcpy((char *)Type,"decimal");
if (DataType == SQL_NUMERIC)
strcpy((char *)Type,"numeric");
if (DataType == SQL_BIT)
strcpy((char *)Type,"bit");
if (DataType == SQL_TINYINT)
strcpy((char *)Type,"tiny integer");
if (DataType == SQL_SMALLINT)
strcpy((char *)Type,"small integer");
if (DataType == SQL_INTEGER)
strcpy((char *)Type,"integer");
if (DataType == SQL_BIGINT)
strcpy((char *)Type,"big integer");
if (DataType == SQL_REAL)
strcpy((char *)Type,"real");
if (DataType == SQL_FLOAT)
strcpy((char *)Type,"float");
if (DataType == SQL_DOUBLE)
strcpy((char *)Type,"double");
if (DataType == SQL_BINARY)
strcpy((char *)Type,"binary");
if (DataType == SQL_VARBINARY)
strcpy((char *)Type,"variable binary");
if (DataType == SQL_LONGVARBINARY)
strcpy((char *)Type,"long variable binary");
if (DataType == SQL_DATE)
strcpy((char *)Type,"date");
if (DataType == SQL_TIME)
strcpy((char *)Type,"time");
if (DataType == SQL_TIMESTAMP)
strcpy((char *)Type,"timestamp");
return Type;
}
[LISTING TEN]
///////////////////////////////////////////////////
// FILE NAME: blob.h TITLE: SQL blob class
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
///////////////////////////////////////////////////
#ifndef __BLOB_H
#define __BLOB_H
#ifndef __DBOPTION_H
#include "dboption.h"
#endif
#ifndef __REQUEST_H
#include "request.h"
#endif
/* 'dbBlob' is a child of 'dbRequest' */
class dbBlob : public dbRequest {
protected:
HSTMT hstmt;
public:
RETCODE Status;
dbBlob(HENV, HDBC, DataBaseOptions *, HSTMT, UCHAR *);
virtual ~dbBlob();
};
#endif
[LISTING ELEVEN]
///////////////////////////////////////////////////
// FILE NAME: blob.cpp TITLE: database blob
// AUTHOR: Ken North Resource Group, Inc.
// 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
// SYNOPSIS: implementation of SQL blob class
///////////////////////////////////////////////////
#include <windows.h>
#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include "sql.h"
#include "sqlext.h"
#include "sqldefs.h"
#include "sqldb.h"
#include "connect.h"
#include "dboption.h"
#include "rqoption.h"
#include "request.h"
#include "blob.h"
/* Constructor for the 'dbBLOB' class: */
dbBlob :: dbBlob( HENV envhandle,
HDBC connhandle,
DataBaseOptions *opt,
HSTMT hStatement,
UCHAR *SQLstring)
: dbRequest( envhandle, connhandle, opt, SQLstring)
{
hstmt = hStatement;
}
///////////////////////////////////////////////////
// FUNCTION NAME: ~dbBLOB
// SYNOPSIS: destructor for dbBLOB
///////////////////////////////////////////////////
dbBlob::~dbBlob()
{
}
[LISTING TWELVE]
///////////////////////////////////////////////////////////////////
// FILE NAME: SQLstruc.cpp TITLE: SQL data dictionary
// AUTHOR: Ken North Resource Group, Inc., 2604B El Camino Real, #351
// copyright(c)1992 Carlsbad, CA 92008
// SYNOPSIS: display dictionary (structure) info for SQL table
///////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <windows.h>
#include "qeapi.h"
#ifndef __QEDEFS_H
#include "qedefs.h" /* QELIB definitions */
#endif
#ifndef __SQLDB_H
#include "sqldb.h" /* database class */
#endif
#ifndef __CONNECT_H
#include "connect.h"
#endif
#ifndef __RQOPTION_H
#include "rqoption.h" /* request options */
#endif
#ifndef __DATASRC_H
#include "datasrc.h" /* data source */
#endif
#ifndef __REQUEST_H
#include "request.h"
#endif
#ifndef __COLUMN_H
#include "column.h"
#endif
#ifndef __BLOB_H
#include "blob.h"
#endif
#ifndef __COLDESC_H
#include "coldesc.h" /* column or field description */
#endif
#include "sqlstruc.h"
static char DBDriver [9];
static char DBDictPath[80];
static char DBTable[31];
static char DBUserid[31];
static char DBPassword[PASSWORDLEN];
static char SQLString[MAXSQL];
static char FldName[DISPLAYLEN+1];
char *driver;
char *table;
char *dict;
char *userid;
char *password;
ColumnDesc *fi;
void main (int argc, char *argv[])
{
dbDataBase *db;
dbConnection *connection;
dbRequest *request;
dbColumn *column;
dbBlob *BLOB;
HSTMT hstmt;
POINTER statement;
// exceeds max SQL stmt. length
BOOL ExceedsMax=FALSE;
int nColumns; /* number of columns */
int ColumnPosition;
int ColumnCount;
int flen; /* string length of field name */
int i; /* blank fill loop index */
int TotalLength; /* sum of field lengths */
char *ColumnDataType;
memset(DBDriver,'\x0',sizeof(DBDriver));
memset(DBDictPath,'\x0',sizeof(DBDictPath));
memset(DBTable,'\x0',sizeof(DBTable));
memset(DBUserid,'\x0',sizeof(DBUserid));
memset(DBPassword,'\x0',sizeof(DBPassword));
memset(SQLString,'\x0',sizeof(SQLString));
/* point to strings */
table = &DBTable[0];
dict = &DBDictPath[0];
driver = &DBDriver[0];
userid = &DBUserid[0];
password = &DBPassword[0];
/* data source includes a driver and path */
/* Using ODBC, data source info is in a file */
/* For this example, prompt for relevant info */
DataSource source;
memset(source.DBDictPath,'\x0',sizeof(source.DBDictPath));
memset(source.DBDataPath,'\x0',sizeof(source.DBDataPath));
memset(source.DBDriver,'\x0',sizeof(source.DBDriver));
memset(source.DBUserid,'\x0',sizeof(source.DBUserid));
memset(source.DBDataBase,'\x0',sizeof(source.DBDataBase));
memset(source.DBServer,'\x0',sizeof(source.DBServer));
db = new(dbDataBase);
db->StatusReturned = SQL_SUCCESS;
/* define options for this database */
db->Options.AllocConnHandle = FALSE;
db->Options.AllocEnvHandle = FALSE;
db->Options.AllocStmtHandle = FALSE;
db->Options.UseCursor = FALSE;
db->opt = &db->Options; /* point to database options */
GetArgs(argc,argv[1],argv[2],argv[3],
table,dict,driver);
/* make selection of database type */
strcpy (source.DBDriver, driver);
/* for dBASE and Paradox databases, the table structure */
/* or dictionary info is in the same directory as data */
strcpy (source.DBDictPath, DBDictPath);
strcpy (source.DBDataPath, DBDictPath);
/*****************************/
/* connect to datasource */
/*****************************/
connection = new(dbConnection);
connection->hdbc = connection->Connect( source, password );
if ((connection->hdbc == NOHANDLE))
{
printf ("\n<SQLStruc> ERROR %d: Unable to connect to %s",
connection->hdbc, source.DBDriver);
exit (1);
}
/*****************************/
/* statement / request */
/*****************************/
strcpy(SQLString,"SELECT * FROM ");
strcat(SQLString,source.DBDataPath);
/* terminate the SQL string */
SQLString[strlen(SQLString)] = '\x5c';
strcat(SQLString,DBTable);
request = new dbRequest(connection->hdbc, db->opt, SQLString);
/* define options for this request */
request->OptionInfo.UseDirectExec = TRUE;
request->OptionInfo.UseNamedCursor = FALSE;
request->OptionInfo.UseTransaction = FALSE;
/* build a valid SQL request/statement string */
/* Note: statement length may be an issue in some */
/* SQL libraries */
statement =
request->BuildRequest(ExceedsMax);
request->hstmt = request->ExecuteStatement(statement);
nColumns = request->FindNumberOfColumns(request->hstmt);
ColumnPosition = 0;
ColumnCount = 0;
TotalLength = 0;
column = new dbColumn( connection->hdbc, db->opt,
request->hstmt, SQLString, ColumnPosition);
/************************/
/* get description */
/************************/
printf ("\nStructure for table: %s\n", DBTable);
printf ("Field Field Name Type Width Dec\n");
db->StatusReturned = SQL_SUCCESS;
do
{
/* point to column descriptor */
fi = &column->ColumnInfo[ColumnPosition];
db->StatusReturned = SQL_SUCCESS;
db->StatusReturned =
column->Describe(ColumnPosition+1, fi);
if ((db->StatusReturned != SQL_SUCCESS))
{
if (db->StatusReturned == ENDFILE)
{
break;
}
else
{
printf
("\n<SQLStruc> ERROR: getting column data for
%s\n", SQLString);
exit (1);
}
};
ColumnCount++; /* update field count */
TotalLength +=
column->ColumnInfo[ColumnPosition].Length;
/* data type is specific to the SQL library */
ColumnDataType = column->DecodeQEDataType(fi->DataType);
strcpy(FldName,fi->Name);
flen = strlen(fi->Name);
for ( i=flen; i < DISPLAYLEN; i++ );
{
FldName[i]='\x20';
}
FldName[DISPLAYLEN]='\x0';
printf ("\n %3d %20.20s %12.12s %5d %2d", ColumnCount,
FldName, ColumnDataType,
fi->Length, fi->Scale);
ColumnPosition++;
}
while (ColumnPosition < nColumns);
printf ("\nTotal: %d\n",TotalLength);
// terminate request
db->StatusReturned =
request->TerminateStatement(request->hstmt);
/************************/
/* disconnect */
/************************/
db->StatusReturned = connection->Disconnect();
if ((db->StatusReturned != SQL_SUCCESS))
{
printf ("\n<SQLStruc> ERROR %d: Disconnecting from %s",
db->StatusReturned, source.DBDriver);
exit (1);
}
}
/**************************************************
* FUNCTION NAME: GetArgs
* AUTHOR: Ken North
* SYNOPSIS: get table name and dictionary path from command line or user
***************************************************/
void GetArgs(int argc, char *arg1, char *arg2,
char *arg3, char *table, char *dict, char *driver)
{
static char name [80];
if (argc > 1)
{
memset(name,'\x0',sizeof(name));
if (*arg1 == '?')
{
printf("\n Usage: SQLSTRUC table \
dictionarypath\n");
printf ("\n SQLSTRUC SQL \
data definitions\n");
printf(" (Requires QELIB DLL\
or QExxx.DLL)\n\n");
printf
(" To display a data dictionary screen, \
type:\n");
printf
(" SQLSTRUC table dictpath driver| \
MORE\n\n");
printf
(" To list to a file, type:\n");
printf
(" SQLSTRUC table dictpath driver> \
listfile\n\n\n");
exit(1);
}
strcpy (name, arg1);
strupr (name); /* translate to upper case */
strcpy(table,name) ; /* copy name to table */
memset(name,'\x0',sizeof(name));
}
if (argc > 2)
{
strcpy (name, arg2);
strupr (name); /* translate to upper case */
strcpy(dict,name) ;
memset(name,'\x0',sizeof(name));
}
if (argc > 3)
{
strcpy (name, arg3);
strupr (name); /* translate to upper case */
strcpy(driver,name) ;
}
if (argc < 2)
{
printf ("Table name ? ");
gets (name);
strupr (name); /* translate to upper case */
strcpy(table,name) ;
memset(name,'\x0',sizeof(name)); /* zero name */
printf ("Dictionary pathname ? ");
gets (name);
strupr (name);
strcpy(dict,name) ;
memset(name,'\x0',sizeof(name)); /* zero name */
printf ("Database driver ? ");
gets (name);
strupr (name);
strcpy(driver,name) ;
}
}
End Listings