In this appendix, we take a close look at ODBC, which is a part of both DAO and ADO and probably will be for some time to come, despite Microsoft’s desire to replace all previous database technologies with OLE DB and ADO.
ODBC is part of DAO in the sense that DAO supports ODBC workspaces for connecting to ODBC providers. Also, ODBC is part of OLE DB in the sense that the first OLE DB data provider was for ODBC data sources and this is still the most flexible OLE DB provider.
Our discussion of ODBC will be fairly detailed, but it will not be reference-like. However, you should feel free to skim through this appendix for whatever information suits your particular needs. If you get more deeply involved in database connectivity, you may find that some of this information will prove useful later on.
Incidentally, all of the code examples in this chapter are available on my web site: http://www.romanpress.com.
Open Database Connectivity, or ODBC for short, is an Application Programming Interface (API) for connecting to databases of various types. (An API is essentially just a set of functions, also called services, for performing various tasks. These functions are usually contained in one or more dynamic link libraries (DLLs).) The term database is used here in a very general sense to refer not only to traditional relational databases, such as Access or FoxPro databases, but also to less traditional “databases” such as delimited text files or Excel worksheets.
Typically, the functions in the ODBC API are implemented in database-specific ODBC drivers. In this way, an application is shielded from having to know the specifics of the various types of databases.
Figure C-1 shows the components involved in the use of ODBC.
Since most data access is done using the SQL language, the primary ODBC-related task for an application is to submit SQL statements to the Driver Manager, which sends the commands to the appropriate driver and also processes any data that is returned as a result of the SQL statements.
The purpose of the ODBC Driver Manager is to manage communication between the application and the driver. The application communicates directly with the Driver Manager, which in turn either processes the command or sends it on (with or without some modification) to the driver. (It is possible for an application to communicate directly with a driver, but this is not usual.)
Generally, the Driver Manager just passes API function calls from the application to the correct driver. However, it does implement some API functions and also performs some basic error checking. In particular, it is responsible for implementing the following driver/data source information functions:
Returns information about a data source
Lists driver descriptions and attributes
Determines whether a given driver supports a given ODBC function
The Driver Manager is also responsible for managing the connection to and disconnection from an ODBC driver. In particular, when an application wants to use a particular driver, the application calls one of the following connection functions:
Establishes a connection to a driver and a data source
Establishes a connection using a connection string
Establishes a connection iteratively
Each of these functions must include information about the
driver in its parameters (in different forms, however). Using this
driver information, the Driver Manager loads the driver (if it is not
already loaded) and calls the appropriate connection function
(SQLConnect
, SQLDriverConnect
, or SQLDriverBrowse
) in the driver.
When the application is done using the driver, it calls
SQLDisconnect
. The Driver Manager
passes this call to the driver, which disconnects from the data
source.
An ODBC driver is a code component that implements the functions in the ODBC API. Each driver is specific to a particular database type. Drivers expose the capabilities of the underlying database management system (DBMS) but do not, in general, enhance its capabilities. The main exception is that drivers for DBMSs that do not have standalone database engines, as is the case with dBASE, Xbase, and ASCII text, for example, must implement a database engine that supports a minimal amount of SQL.
In particular, an ODBC driver must implement the following tasks (among others):
Connecting to and disconnecting from the data source.
Sending data to and retrieving data from the data source.
Checking for API function errors that are not checked by the Driver Manager.
Submitting SQL statements to the data source for execution. For this, the driver may need to modify the ODBC-style SQL statements to a form of SQL that the DBMS understands.
In general, there are two types of ODBC drivers. A file-based driver accesses the physical data in the database directly. Thus, it must process not only ODBC function calls, but also SQL statements. Put another way, a file-based driver must also be a database engine that can process ODBC SQL (at a minimum). For example, dBASE drivers are file-based drivers because dBASE does not provide a standalone database engine the driver can use.
By contrast, a DBMS-based driver accesses the physical data only through a separate database engine. In this case the driver processes ODBC calls but passes SQL statements to the database engine for processing. For example, Microsoft Access provides a standalone database engine called Jet, so an Access driver can be DBMS-based. (There are also file-based Access database drivers that communicate directly with MDB files.)
The advantage of DBMS-based drivers is that they can accept and pass along the DBMS’s specific brand of SQL. For instance, a DBMS-based driver for Microsoft Access can pass Access SQL statements to the Access database ( Jet) engine for processing. On the other hand, a file-based Access driver, which contains its own proprietary database engine that accesses MDB files directly, may support only ODBC SQL, in which case attempts to pass Access-specific SQL statements to the driver are likely to result in errors.
A data source is, in general, a source of data. However, this term is
one of the most abused and inconsistently misused terms in
database-related programming (at least in Microsoft’s arsenal). For
instance, when the data is contained in a text file, then the term
“data source” refers simply to the physical data in the file.
Similarly, when the data is contained in an Access database file
(extension .mbd) that is being
accessed by a file-based driver, the term data source refers to the
MDB file. On the other hand, when the data are contained in an Access
database file that is being accessed by a DBMS-based driver, then the
data source is considered to be the combination of the Access DBMS and
the MDB file. On the other hand, in the context of the new VB6
DataBinding object model, the term data source refers to a source for
the data binding, which is often a VB6 class module that has its
DataSourceBehavior property set to vbDataSource
. In this case, the data source
itself contains no data whatsoever!
Thus, just what constitutes a data source depends upon the circumstances. In fact, since a data source is always associated with a particular driver under ODBC, we will usually think of the pair together. This view is supported by the fact that when configuring a data source using the ODBC Administrator, we are first required to select a driver.
The term data source is also sometimes used (unfortunately) to stand for the description of a data source—that is, the name and path of the database, password, user name, connection attributes, and so on. What a mess.
The ODBC literature uses the term Data Source Name ( DSN) quite frequently. Unfortunately, it does not refer simply to the name of the data source! Rather, it refers to a description of the data source, the accompanying driver, and the attributes of a connection between the two. For instance, a DSN includes the name of the data source, the complete path of the data source, the name of the driver, and details about the connection to the data source, such as whether or not the connection is read-only. We will see examples of DSNs a little later. The important thing to keep in mind is that the name DSN is quite misleading. Perhaps a better term would have been Data Connection Description (DCD).
Data sources are said to fall into two categories: machine data sources and file data sources. Note, however, that it is really the DSNs that fall into these categories. The difference is in where and how the DSN (and not the data source itself ) is stored.
For a machine data source, the DSN is stored in the system registry of a machine under a specific name, called the Data Source Name name (DSN name). A machine data source can be registered under one of two registry keys:
HKEY_LOCAL_MACHINE/SOFTWARE/ODBC/ODBC.INI
HKEY_CURRENT_USER/SOFTWARE/ODBC/ODBC.INI
In the former case, the DSN is available to all users of the
machine. In the latter case, the DSN is available only to the user
under whose name it is registered. When a DSN is stored in the
HKEY_LOCAL_MACHINE
key, the
data source is referred to as a system data
source , although again this term should really be applied
to the DSN. When the DSN is stored in the HKEY_CURRENT_USER
key, the data source
(actually DSN) is referred to as a user data
source .
Incidentally, the registry key HKEY_LOCAL_MACHINE/SOFTWARE/ODBC/ODBCINST.INI
contains information about each installed ODBC component,
including drivers. This is a good place to find the filename of a
driver, should you be interested.
For a file data source, the DSN is kept in an ordinary text file, with extension .dsn, and is accessible to anyone with access to the file. This is so that a file data source (that is, a file DSN) is not registered to any one user or machine. Thus, a file DSN does not have a DSN name per se (under which it is registered). It does have a filename, of course.
The main advantage of a file data source is that it can be copied to any machine, so that identical data sources can be used by several machines. A file data source can also be shared by more than one application.
DSNs are generally created by the user with a program called the ODBC Administrator . This program is accessed by clicking on the ODBC icon in the Windows Control Panel. The opening dialog box is shown in Figure C-2.
Once the type of DSN (User, System, or File) is chosen and the user clicks the Add button, the dialog box in Figure C-3 is displayed, prompting the user for the name of the driver.
The ODBC Administrator then calls the driver so it can display any of its dialog boxes that request specific information required by the driver to connect to the data source. (Thus, these dialog boxes vary from driver to driver.) After the user enters the information, the DSN data is stored in the appropriate place (the registry or a DSN file).
It is helpful to take a look at a few examples of DSNs created using the ODBC Administrator.
Here is an example of the registry entries for a system DSN consisting of an Excel workbook. The DSN name is ConnectExcel:
[HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INIConnectExcel] "Driver"="C:\WINNT\System32\odbcjt32.dll" "DBQ"="d:\bkado\connect.xls" "DefaultDir"="d:\bkado" "Description"="An example Excel data source" "DriverId"=dword:00000316 "FIL"="excel 5.0;" "ReadOnly"=hex:00 "SafeTransactions"=dword:00000000 "UID"="" [HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INIConnectExcelEngines] [HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INIConnectExcelEnginesExcel] "ImplicitCommitSync"="Yes" "MaxScanRows"=dword:00000008 "Threads"=dword:00000003 "UserCommitSync"="Yes" "FirstRowHasNames"=hex:01
As you can see, the Driver value entry holds the name of the
ODBC driver for Excel. The DBQ value entry gives the name of the
Excel workbook, which is the database in this case. Each worksheet
in the workbook is a database table. (For some reason, the value
of FIL
is "excel
5.0"
, even though the version of Excel
that I used here is Excel 97.) The EnginesExcel
subkey reports, among
other things, whether the Excel tables (worksheets) use the first
row for field names.
The ODBC Administrator dialog boxes that created this data source are shown in Figures C-4 and C-5.
The contents of an Excel file DSN are shown here:
[ODBC] DRIVER=Microsoft Excel Driver (*.xls) UID=admin UserCommitSync=Yes Threads=3 SafeTransactions=0 ReadOnly=0 PageTimeout=5 MaxScanRows=8 MaxBufferSize=512 ImplicitCommitSync=Yes FIL=excel 5.0 DriverId=790 DefaultDir=D:kado DBQ=D:BkAccessIIConnect.xls
Note that this is not as extensive as the system DSN. For
instance, it does not include the FirstRowHasNames
value.
Here is an example for a text data source. In this case, a “table” is a text file with extension .txt, .csv, .tab, or .asc.
[HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INIConnectText] "Driver"="C:\WINNT\System32\odbcjt32.dll" "DefaultDir"="D:\bkado" "Description"="A text data source" "DriverId"=dword:0000001b "FIL"="text;" "SafeTransactions"=dword:00000000 "UID"="" [HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INIConnectTextEngines] [HKEY_LOCAL_MACHINESOFTWAREODBCODBC.INIConnectTextEnginesText] "Extensions"="txt,csv,tab,asc" "ImplicitCommitSync"="Yes" "Threads"=dword:00000003 "UserCommitSync"="Yes"
Note that nowhere in the registry is there a reference to the actual table (text file) or tables for this data source. This information is placed in a special text file called schema.ini that is created by the ODBC Administrator. The file is placed in the directory DefaultDir. Here are the contents of the schema.ini file, which in this case actually describes two separate text connections:
[donna.txt] ColNameHeader=True Format=TabDelimited MaxScanRows=25 CharacterSet=OEM Col1=FIRSTNAME Char Width 255 Col2=LASTNAME Char Width 255 [textfile.csv] ColNameHeader=False Format=CSVDelimited MaxScanRows=25 CharacterSet=OEM Col1=F1 Char Width 255 Col2=F2 Char Width 255
Note that if new text “tables” are added to the connection, additional sections are created in the schema.ini file. The ODBC dialog boxes that created the first connection are shown in Figures C-6 and C-7.
It is not my intention to go into the details of the ODBC API functions. However, I do want to discuss the functions briefly that are used to establish a data-source connection, since this will shed some light on the issues of DSNs and the infamous connection string.
The ODBC API has three functions for establishing data-source
connections: SQLConnect
,
SQLDriverConnect
, and SQLBrowseConnect
. I will briefly discuss
the first two.
SQLConnect
is the simplest connection function. The parameters
to this function consist of a DSN and optionally a user ID and
password. This function is the best choice when the DSN contains all
of the information required for the connection. Note that this is
not always the case. For instance, suppose that the connection
requires one password to log on to a server and a second password to
log onto a specific database on the server. The first password can
be included as an argument to SQLConnect
, but the second password must
be stored in the DSN. If you don’t want to store a password in a
DSN, the DSN will not be sufficient to make the connection, and so
the SQLConnect
function will
not be appropriate.
Since SQLConnect
does not
interact with the user (unlike the other connection functions), it
is the correct choice when the programmer wants to write his own
interaction code (such as prompting the user for a user ID or passwords).
A connection string is a text string that contains information used for
establishing a data-source connection. Note, however, that a
connection string may or may not contain all of the required
information (just as a DSN may not be complete). A connection string
consists of a series of keyword/value pairs separated by semicolons.
As you will see, a connection string is used by SQLDriverConnect
. Note that SQLConnect
does not use a connection
string. Since DSNs serve essentially the same purpose, connection
strings and DSNs are basically just two sides of the same coin.
(In fact, connection strings are built from DSNs by ODBC.)
When the parameters to SQLConnect
—a DSN, a password, and a user
ID—are not sufficient to make the desired connection, the SQLDriverConnect
function may do the job. There are two reasons to use
SQLDriverConnect
rather than
SQLConnect
. First, if a system
DSN does not contain sufficent connection information, it is much
simpler to construct a custom connection string in code than it is
to alter the registry entries in a DSN. (For a file DSN, this issue
is mitigated somewhat, but it is still easier to create a connection
string in code than to open and alter a text file.) Second,
SQLDriverConnect
is capable of
prompting the user for connection information by displaying ODBC
dialog boxes.
To illustrate, if a driver requires two passwords (as discussed earlier), then a connection string could contain these passwords (along with other data):
UID=SRoman;ServerPWD=SubRosa;DBPWD=Secret;
As we mentioned, if a connection string is not complete,
SQLDriverConnect
may prompt the
user for additional connection information. For
example, if the connection string is:
DSN=ConnectToWhatever;
this might cause the driver to display a dialog box asking for the necessary user ID and password.
In addition, if SQLDriverConnect
receives an empty
connection string, the Driver Manager displays a dialog box
prompting the user for the correct DSN.
You may be able to get some limited help for an ODBC driver by starting the DSN creation process through the ODBC Administrator and then clicking the Help button once a driver-specific dialog box appears. This brings up the ODBC Microsoft Desktop Database Drivers Help file. However, this information is at best sketchy and often misleading. For instance, under the topic Section C.4.6, the help file says that a connection string includes the following keywords:
Name of the data source
Name of the directory
An integer ID for the driver
File type
However, as you will see in the upcoming examples, the DBQ value is the name of the directory for the Microsoft Text Driver, but not the name of the actual workbook for the Microsoft Excel Driver! The help file also does not give any indication as to when or whether these keywords are always required. Nevertheless, the information contained in the help file can be very useful.
It is clear that in order to use ODBC effectively, the programmer may need to know what drivers and data sources exist on a particular computer. This information is accessible through a few ODBC API calls.
The following code includes a procedure called ListODBCSources
, which prints (to the
Immediate window) a list of all data sources on a system, and ListODBCDrivers
, which prints a list of ODBC
drivers on the system. This code can be placed in an Access code
module:
Const SQL_NULL_HANDLE = 0 Const SQL_HANDLE_ENV = 1 Const SQL_FETCH_NEXT = 1 Const SQL_FETCH_FIRST = 2 Const SQL_SUCCESS = 0 Const SQL_ATTR_ODBC_VERSION = 200 Const SQL_OV_ODBC2 = 2 Const SQL_IS_INTEGER = -6 Dim nRetCode As Long Declare Function SQLDrivers Lib "odbc32.dll" (ByVal _ EnvironmentHandle As Long, ByVal Direction As Integer, _ ByVal DriverDescription As String, ByVal BufferLength1 As Integer, _ DescriptionLengthPtr As Integer, ByVal DriverAttributes As String, _ ByVal BufferLength2 As Integer, AttributesLengthPtr As Integer) _ As Integer ' Note that pointers to numbers are passed as numbers by reference! Declare Function SQLDataSources Lib "odbc32.dll" (ByVal _ EnvironmentHandle As Long, ByVal Direction As Integer, _ ByVal ServerName As String, ByVal BufferLength1 As Integer, _ NameLength1Ptr As Integer, ByVal Description As String, _ ByVal BufferLength2 As Integer, NameLength2Ptr As Integer) As Integer Declare Function SQLFreeHandle Lib "odbc32.dll" (ByVal _ HandleType As Integer, ByVal Handle As Long) As Integer Declare Function SQLAllocHandle Lib "odbc32.dll" (ByVal _ HandleType As Integer, ByVal InputHandle As Long, _ OutputHandlePtr As Long) As Integer Declare Function SQLSetEnvAttr Lib "odbc32.dll" (ByVal _ EnvironmentHandle As Long, ByVal EnvAttribute As Long, _ ByVal ValuePtr As Long, ByVal StringLength As Long) As Integer Declare Function SQLDisconnect Lib "odbc32.dll" (ByVal _ ConnectionHandle As Long) As Integer Public Function Trim0(sName As String) As String ' Keep left portion of string sName up to first 0. Dim x As Integer x = InStr(sName, Chr$(0)) If x > 0 Then Trim0 = Left$(sName, x - 1) Else Trim0 = sName End Function Private Sub ListODBCSources( ) ' Prints a list of ODBC data soruces/drivers on system Dim lHEnv As Long Dim sServerName As String * 32 Dim sDescription As String * 128 Dim nServerNameLength As Integer Dim nDescriptionLength As Integer ' Allocate an environment handle. nRetCode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, lHEnv) ' Set ODBC behavior nRetCode = SQLSetEnvAttr(lHEnv, SQL_ATTR_ODBC_VERSION, _ SQL_OV_ODBC2, SQL_IS_INTEGER) ' Put first data source name in sServerName nRetCode = SQLDataSources(lHEnv, SQL_FETCH_FIRST, sServerName, _ Len(sServerName), nServerNameLength, sDescription, _ Len(sDescription), nDescriptionLength) Debug.Print "DATA SOURCE / DRIVER" Do While nRetCode = SQL_SUCCESS Debug.Print Left$(sServerName, _ nServerNameLength) & " / " & Trim0(sDescription) ' Next data source nRetCode = SQLDataSources(lHEnv, SQL_FETCH_NEXT, _ sServerName, Len(sServerName), nServerNameLength, _ sDescription, Len(sDescription), nDescriptionLength) Loop nRetCode = SQLFreeHandle(SQL_HANDLE_ENV, lHEnv) End Sub ' ----------------------------------------- Private Sub ListODBCDrivers( ) ' Prints a list of ODBC drivers on system Dim lHEnv As Long Dim sDriverDesc As String * 1024 Dim sDriverAttr As String * 1024 Dim sDriverAttributes As String Dim nDriverDescLength As Integer Dim nAttrLength As Integer Dim x As Integer Dim sAll As String ' Allocate an environment handle. nRetCode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, lHEnv) ' Set ODBC behavior nRetCode = SQLSetEnvAttr(lHEnv, SQL_ATTR_ODBC_VERSION, _ SQL_OV_ODBC2, SQL_IS_INTEGER) ' Get first driver nRetCode = SQLDrivers(lHEnv, SQL_FETCH_FIRST, sDriverDesc, _ Len(sDriverDesc), nDriverDescLength, sDriverAttr, _ Len(sDriverAttr), nAttrLength) sAll = "" Do While nRetCode = SQL_SUCCESS ' Replace NULL separators with colons sDriverAttributes = Left$(sDriverAttr, nAttrLength - 1) Do x = InStr(sDriverAttributes, Chr$(0)) If x = 0 Then Exit Do sDriverAttributes = Left$(sDriverAttributes, x - 1) & _ " : " & Mid$(sDriverAttributes, x + 1) Loop sAll = sAll & Left$(sDriverDesc, nDriverDescLength) & _ " / " & sDriverAttributes & vbCrLf ' Next data source nRetCode = SQLDrivers(lHEnv, SQL_FETCH_NEXT, sDriverDesc, _ Len(sDriverDesc), nDriverDescLength, sDriverAttr, _ Len(sDriverAttr), nAttrLength) Loop Debug.Print "ODBC Drivers" Debug.Print sAll nRetCode = SQLFreeHandle(SQL_HANDLE_ENV, lHEnv) End Sub
The output produced by running ListODBCSources
on my system is:
DATA SOURCE / DRIVER MS Access 7.0 Database / Microsoft Access Driver (*.mdb) Visual FoxPro Tables / Microsoft Visual FoxPro Driver Visual FoxPro Database / Microsoft Visual FoxPro Driver MS Access 97 Database / Microsoft Access Driver (*.mdb) OLE_DB_NWind_Jet / Microsoft Access Driver (*.mdb) OLE_DB_NWind_SQL / SQL Server ConnectExcel / Microsoft Excel Driver (*.xls) ConnectAccess / Microsoft Access Driver (*.mdb) ConnectText / Microsoft Text Driver (*.txt; *.csv)
The output of ListODBCDrivers
is:
ODBC Drivers SQL Server / UsageCount=10 : SQLLevel=1 : FileUsage=0 : DriverODBCVer=02.50 : ConnectFunctions=YYY : APILevel=2 : Setup=sqlsrv32.dll : .01= : s=YYN : DSNConverted=F : CPTimeout=60 : FileExtns=Null Microsoft ODBC Driver for Oracle / UsageCount=3 : SQLLevel=1 : FileUsage=0 : DriverODBCVer=02.50 : ConnectFunctions=YYY : APILevel=1 Microsoft Access Driver (*.mdb) / UsageCount=10 : APILevel=1 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=2 : FileExtns=*.mdb : SQLLevel=0 : s=YYN Microsoft dBase Driver (*.dbf) / UsageCount=6 : APILevel=1 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=1 : FileExtns=*.dbf,*.ndx,*.mdx : SQLLevel=0 : [g= : = : ;g= : g= xxx Microsoft FoxPro Driver (*.dbf) / UsageCount=6 : APILevel=1 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=1 : FileExtns=*.dbf,*.cdx,*.idx,*.ftp : SQLLevel=0 Microsoft Excel Driver (*.xls) / UsageCount=4 : APILevel=1 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=1 : FileExtns=*.xls : SQLLevel=0 Microsoft Paradox Driver (*.db ) / UsageCount=3 : APILevel=1 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=1 : FileExtns=*.db : SQLLevel=0 Microsoft Text Driver (*.txt; *.csv) / UsageCount=4 : APILevel=1 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=1 : FileExtns=*.,*.asc,*.csv,*.tab,*.txt,*.csv : SQLLevel=0 Microsoft ODBC for Oracle / UsageCount=2 : SQLLevel=1 : FileUsage=0 : DriverODBCVer=02.50 : ConnectFunctions=YYY : APILevel=1 : CPTimeout=120 Microsoft Visual FoxPro Driver / UsageCount=2 : APILevel=0 : ConnectFunctions=YYN : DriverODBCVer=02.50 : FileUsage=1 : FileExtns=*.dbc,*.dbf : SQLLevel=0
Let us briefly describe the ODBC functions used in these procedures. You can skip this material if it does not interest you.
Before using the ODBC functions we are interested in, we must
first get a handle to the ODBC environment. Obtaining an environment handle is done
by calling SQLAllocHandle
, whose
Visual Basic declaration is:
Declare Function SQLAllocHandle Lib "odbc32.dll" ( ByVal HandleType As Integer, _ ByVal InputHandle As Long, _ OutputHandlePtr As Long) As Integer
The actual call to use is:
nRetCode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, lHEnv)
The return value is an error code or 0 if no error has
occured, in which case lHEnv
will
receive the handle as a Long.
Once we have obtained an environment handle, we must set the
environment attribute known as ODBC behavior, using the SQLSetEnvAttr
function, as follows:
' Set ODBC behavior nRetCode = SQLSetEnvAttr(lHEnv, SQL_ATTR_ODBC_VERSION, _ SQL_OV_ODBC2, SQL_IS_INTEGER)
Note the use of the lHEnv
argument to identify the environment handle. This function call sets
the ODBC behavior to ODBC Version 2.x (SQL_OV_ODBC2
). Actually, it does not seem
to matter whether we set the behavior to ODBC Version 2 or Version 3
(SQL_OV_ODBC3
) as long as we set
it to one of these values!
To get information about the installed ODBC drivers on a system, we use the SQLDrivers
function. The declaration for this function
is:
Declare Function SQLDriverConnect Lib "odbc32.dll" ( _ ByVal ConnectionHandle As Long, ByVal WindowHandle As Long, _ ByVal InConnectionString As String, ByVal StringLength1 As Integer, _ ByVal OutConnectionString As String, ByVal BufferLength As Integer, _ StringLength2Ptr As Integer, ByVal DriverCompletion As Integer) As Integer
The following is the complete procedure to list all drivers and their attributes in a text box. (This procedure and the following ones are bare-bones, with no error checking. Feel free to augment them for your own use.)
Private Sub ListODBCDrivers( ) Dim lHEnv As Long Dim sDriverDesc As String * 1024 Dim sDriverAttr As String * 1024 Dim sDriverAttributes As String Dim nDriverDescLength As Integer Dim nAttrLength As Integer Dim x As Integer Dim sAll As String txtDrivers = "" ' Allocate an environment handle. nRetCode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, lHEnv) ' Set ODBC behavior nRetCode = SQLSetEnvAttr(lHEnv, SQL_ATTR_ODBC_VERSION, _ SQL_OV_ODBC2, SQL_IS_INTEGER) ' Get first driver nRetCode = SQLDrivers(lHEnv, SQL_FETCH_FIRST, sDriverDesc, _ Len(sDriverDesc), nDriverDescLength, sDriverAttr, _ Len(sDriverAttr), nAttrLength) sAll = "" Do While nRetCode = SQL_SUCCESS ' Replace NULL separators between atributes with colons sDriverAttributes = Left$(sDriverAttr, nAttrLength - 1) Do x = InStr(sDriverAttributes, Chr$(0)) If x = 0 Then Exit Do sDriverAttributes = Left$(sDriverAttributes, x - 1) _ & " : " & Mid$(sDriverAttributes, x + 1) Loop ' Save it sAll = sAll & Left$(sDriverDesc, nDriverDescLength) _ & " / " & sDriverAttributes & vbCrLf ' Next data source nRetCode = SQLDrivers(lHEnv, SQL_FETCH_NEXT, sDriverDesc, _ Len(sDriverDesc), nDriverDescLength, sDriverAttr, _ Len(sDriverAttr), nAttrLength) Loop txtDrivers = sAll nRetCode = SQLFreeHandle(SQL_HANDLE_ENV, lHEnv) End Sub
Some of the driver attributes are worth discussing briefly:
Gives the version of ODBC that the driver supports. Note that even though the drivers on my system are Version 3.5 or later, their ODBC Versions are only 2.5. Thus, they support only ODBC 2.5.
Describes, in general terms, the level of compliance of the driver to SQL. Level 0 is basic SQL-92 compliance. Level 1 is FIPS127-2 Transitional (whatever that is); Level 2 is SQL-92 Intermediate; Level 3 is SQL-92 Full.
Indicates which of the three connection-related
functions (SQLConnect
, SQLDriverConnect
, or SQLBrowseConnect
) are supported by this driver. The value has
the form XXX, where X is Y or N. Thus, a value of YYN means
that the driver supports SQLConnect
and SQLDriverConnect
but not SQLBrowseConnect
.
For file-based drivers (that access the physical data directly), indicates which filename extensions the driver recognizes.
Indicates how a file-based driver views the data in the physical database. A value of 0 indicates that the driver is not file-based. A value of 1 indicates that a file-based driver treats data-source files as tables. A value of 2 indicates that the driver treats the data files as databases.
The process of getting a list of all data sources is
quite similar. It uses the function SQLDataSources
, whose syntax is similar to SQLDrivers. The Visual
Basic declaration is:
Declare Function SQLDataSources Lib "odbc32.dll" (ByVal _ EnvironmentHandle As Long, ByVal Direction As Integer, _ ByVal ServerName As String, ByVal BufferLength1 As Integer, _ NameLength1Ptr As Integer, ByVal Description As String, _ ByVal BufferLength2 As Integer, NameLength2Ptr As Integer) As Integer
The complete code is:
Private Sub ListODBCSources( ) Dim lHEnv As Long Dim sServerName As String * 32 Dim sDescription As String * 128 Dim nServerNameLength As Integer Dim nDescriptionLength As Integer lstDataSources.Clear ' Allocate an environment handle. nRetCode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, lHEnv) ' Set ODBC behavior nRetCode = SQLSetEnvAttr(lHEnv, SQL_ATTR_ODBC_VERSION, _ SQL_OV_ODBC2, SQL_IS_INTEGER) ' Put first data source name in sServerName nRetCode = SQLDataSources(lHEnv, SQL_FETCH_FIRST, sServerName, _ Len(sServerName), nServerNameLength, sDescription, _ Len(sDescription), nDescriptionLength) lstDataSources.AddItem "DATA SOURCE / DRIVER" Do While nRetCode = SQL_SUCCESS lstDataSources.AddItem Left$(sServerName, _ nServerNameLength) & " / " & Trim0(sDescription) ' Next data source nRetCode = SQLDataSources(lHEnv, SQL_FETCH_NEXT, _ sServerName, Len(sServerName), nServerNameLength, _ sDescription, Len(sDescription), nDescriptionLength) Loop nRetCode = SQLFreeHandle(SQL_HANDLE_ENV, lHEnv) End Sub
3.144.151.126