Sunteți pe pagina 1din 32

PAGE 1:

ODBC from C Tutorial Part 1


Contents
• Introduction
• Pre-requisites
• Assumptions
• The ODBC API
o ODBC Reference
o ODBC Include Files
o ODBC Defined Macros
o ODBC Handles
o String Arguments
• Allocating and Freeing Handles
• Listing Installed Drivers and Data Sources
o Listing Installed Drivers
o Listing Installed Data Sources
• Setting your Environment
• Connecting to a Driver or Data Source
o SQLDriverConnect
o A Simple Connection
o Format of the ODBC Connection String
o ODBC Defined Connection Attributes
o Returned Connection String
o Full Connection Example
• Driver and Driver Manager Information
• Basic Result-set Generating Functions
• Basic Fetching Results
• Handling and Reporting ODBC Errors and Diagnostics
o Return Statuses
o Retrieving ODBC Diagnostics
o Diagnostic Fields
• Appendix A: Resources

Introduction
This is part 1 of a series of Easysoft tutorials on using ODBC from C.

Pre-requisites
Before you start part 1 of this tutorial you need to ensure you satisfy all the pre-requisites:

1. C

You will need an ANSI C compiler. For this tutorial we used gcc on Linux, but
with a few small alterations the C compilers from Sun, HP, IBM etc will work
just as well.

2. ODBC Driver Manager

You link your application with an ODBC Driver Manager which will:

o provide the C header files that allow you to compile your application.
o load the driver you want to connect to.

We recommend you use the unixODBC driver manager.

You probably have the unixODBC driver manager installed if you have the
odbcinst command (for ODBC drivers from Easysoft, the unixODBC driver
manager is located in /usr/local/easysoft/unixODBC and the odbcinst command in
the bin sub directory of that path).

We used unixODBC 2.2.12 in this tutorial. You can find out the unixODBC
version you are using with:

odbcinst --version

3. ODBC Driver

You will need an ODBC driver and a working database to connect to. Easysoft
can supply ODBC drivers for many databases and operating systems. All Easysoft
ODBC drivers include the unixODBC driver manager.

For this tutorial we used the Easysoft ODBC-ODBC Bridge as the ODBC driver
to access a remote MS SQL Server database from UNIX.

Assumptions
This tutorial does not explain the C language and how to write C. It is expected you
already understand the C programming language, and are able to edit, compile and link
programs.

We have assumed you have a good ODBC API reference to hand (see ODBC Reference)
as this tutorial is not an attempt to just reproduce the ODBC Programmers Reference; it is
more example based.

Operating System

This tutorial was designed on UNIX and we have assumed you are using UNIX too.
However, all the C examples should work equally well on MS Windows and other
operating systems with some minor alterations (e.g. including windows.h on MS
Windows and making the appropriate compiler/linker changes).

ODBC Driver Manager

We have assumed you are using the unixODBC driver manager. All discussion in this
document relating to the location and definition of ODBC data sources is for unixODBC.

The ODBC API


ODBC Reference
The ODBC Application Programming Interface (API) defines the functions your
application can call in the ODBC driver manager and the arguments they take.

The reference for the ODBC API is The Microsoft ODBC 3.0 Programmers Reference
Volume 1 and 2 (ISBN 1-57231-516-4) although you may have some trouble finding this
now. You can also find the entire ODBC reference in older versions of the ODBC SDK
and online at Microsoft's web site.

ODBC Include Files


For C programmers the definitions you require are held in the header files sql.h, sqlext.h,
sqltypes.h, sqlucode.h and odbcinst.h. For unixODBC you will find these installed in
<installpath>/include. Further explanation of these headers can be found later.

Normally you only need to include sql.h (which contains most of the definitions you'll
need) and sqlext.h (which contains mostly additions for ODBC 3). sqlucode.h is
automatically included by sqlext.h and sqltypes.h is automatically included from sql.h.
odbcinst.h contains the installers APIs e.g. SQLConfigDataSource and the APIs a drivers
use e.g. SQLWriteFileDSN.
ODBC Defined Macros
If you examine the ODBC header files you'll find tests on a number of macros. The
principal macros examined are:

• ODBCVER

If you do not set this it will default to 0x0351 for ODBC 3.51. If you want to
restrict your application to ODBC 2, you could define ODBCVER=0x0200 when
compiling your code.

• UNICODE

If you define UNICODE then all calls to SQLxxx APIs are changed to SQLxxxW
i.e. your application will be calling the wide ODBC APIs and therefore needs to
pass wide characters. Normally with unixODBC wide characters are 2 bytes
mirroring the ODBC API on MS Windows.

• ODBC_STD

If ODBC_STD is defined then you SQLAllocHandle and SQLAllocEnv are


changed to SQLAllocHandleStd for X/Open compatibility.

There are also a number of convenience macros like SQL_SUCCEEDED (used to test
ODBC API return status, see Handling and reporting ODBC errors and diagnostics) and
macros used to describe something, like SQL_NTS (which specifies a provided string is
null terminated).

ODBC Handles
In ODBC there are four main handle types and you will need to know at least three to do
anything useful:

• SQLHENV - environment handle

This is the first handle you will need as everything else is effectively in the
environment. Once you have an environment handle you can define the version of
ODBC you require, enable connection pooling and allocate connection handles
with SQLSetEnvAttr and SQLAllocHandle.

• SQLHDBC - connection handle

You need one connection handle for each data source you are going to connect to.
Like environment handles, connection handles have attributes which you can
retrieve and set with SQLSetConnectAttr and SQLGetConnectAttr.
• SQLHSTMT - statement handle

Once you have a connection handle and have connected to a data source you
allocate statement handles to execute SQL or retrieve meta data. As with the other
handles you can set and get statement attributes with SQLSetStmtAttr and
SQLGetStmtAttr.

• SQLHDESC - descriptor handle

Descriptor handles are rarely used by applications even though they are very
useful for more complex operations. Descriptor handles will be covered in later
tutorials.

ODBC handles are opaque types and although the ODBC standard does not specifically
say they are pointers to structures they generally are (but you should not rely on this).

String Arguments
A number of the ODBC APIs accept string pointers and return strings to user supplied
buffers. In general, ODBC APIs accepting input string arguments have a pointer
argument followed by a length argument e.g.

SQLRETURN SQLPrepare(SQLHSTMT stmt,


SQLCHAR *StatementText,
SQLINTEGER TextLength)

There are usually two ways of specifying the length of an input string:

1. The length in bytes of the string e.g. "fred" has length 4.


2. The string is null terminated (as is usual in C). You specify null terminated strings
with the ODBC defined macro SQL_NTS.

Where an ODBC API returns a string it is usual for the API to require a pointer to a
buffer and a pointer to an integer (of some sort) to return the length of the returned string
e.g.

SQLRETURN SQLGetCursorName(SQLHSTMT StatementHandle,


SQLCHAR *CursorName,
SQLSMALLINT BufferLength,
SQLSMALLINT *NameLengthPtr)

In the above case you pass a pointer to a buffer to receive the cursor name, the length of
that buffer (so it is not overrun) and a pointer to a SQLSMALLINT in which is written
the length of the returned string. There are a few useful points about the way the ODBC
API works here which is worth mentioning:
1. In general, the buffer must be big enough for the returned string and a terminating
null character. The returned length will not include the terminating null character.
2. APIs like the above generally return SQL_SUCCESS_WITH_INFO, a state of
01004 and a message of "String data, right truncated" if the supplied buffer was
not big enough for the returned string.
3. You do not have to supply a pointer to the returned length (i.e. it may be NULL)
but then if the buffer is truncated you will not know how long big a buffer you
need.
4. Strangely, you do not have to specify a buffer (i.e. it may be NULL) and the
buffer length may be zero. It is not uncommon to see applications do something
like this:
5. SQLCHAR *buffer = NULL;
6. SQLSMALLINT retlen;
7. SQLRETURN ret;
8.
9. ret = SQLGetCursorName(stmt, NULL, 0, &retlen);
10. if (SQL_SUCCEEDED(ret)) {
11. buffer = malloc(retlen + 1); /* add one for null termination
*/
12. SQLGetCursorName(stmt, buffer, retlen + 1, NULL);
13. }
14.

Allocating and Freeing Handles


As described in ODBC Handles there are four types of ODBC handle; environment,
connection, statement and descriptor. Handles must be allocated in a specific order and
each type of handle is used for different purposes. Each handle type has attributes which
you can query and set and a diagnostic stack which can be queried for errors and
diagnostic information.

Each handle type can be allocating using SQLAllocHandle:

SQLRETURN SQLAllocHandle(
SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE *OutputHandlePtr)

HandleType must be one of:

• SQL_HANDLE_ENV - to allocate an environment handle.


• SQL_HANDLE_DBC - to allocate a connection handle.
• SQL_HANDLE_STMT - to allocate a statement handle.
• SQL_HANDLE_DESC - to allocate a descriptor handle.

The InputHandle argument is either SQL_NULL_HANDLE (when allocating


environment handles) or the value of the enclosing handle i.e. you pass an environment
handle when allocating a connection handle and a connection handle when allocating a
statement or descriptor handle.

OutputHandlePtr is a ptr to the handle to be returned.

Similarly, there is the SQLFreeHandle API to free up a handle and its associated
resources:

SQLRETURN SQLFreeHandle(
SQLSMALLINT HandleType,
SQLHANDLE Handle)

Note that handles generally need to be freed in the opposite order to which they were
allocated and that handles cannot be freed if they are in use e.g. you cannot free a
connected connection handle until it is disconnected (see later).

Ignoring descriptors for now, the following code is usually in every ODBC application:

SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);


SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* connect to the data source */
SQLAllocHandle(SQL_HANDLE_ENV, dbc, &stmt);
/* do something with the statement handle e.g. issue sql */
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
/* disconnect */
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);

In ODBC 2.0 descriptor handles did not exist and each handle type had its own allocation
and deallocation API; SQLAllocEnv, SQLFreeEnv, SQLAllocConnect, SQLFreeConnect,
SQLAllocStmt and SQLFreeStmt. You should avoid using these APIs now and use
SQLAllocHandle and SQLFreeHandle instead.

Setting Your Environment


You set environment attributes with SQLSetEnvAttr and retrieve them with
SQLGetEnvAttr.

Once you've allocated your environment handle you specify the ODBC version behavior
you require (in ODBC 3.0 this is compulsory and if you forget to do this and attempt to
use an environment handle before specifying the ODBC behavior your require you will
get a HY010, function sequence error). Currently there are only two choices;
SQL_OV_ODBC2 (for ODBC 2 behavior) and SQL_OV_ODBC3 (for ODBC 3
behavior).
If you ask for ODBC 3 behavior:

• The driver will expect and return ODBC 3 types for time, date and timestamp i.e.
SQL_TYPE_TIME instead of SQL_TIME etc.
• ODBC states returned (SQLSTATE) are ODBC 3 states.
• The CatalogName argument to SQLTables will accept a search pattern.

New applications should always ask for ODBC 3 behavior:

SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);

A few ODBC drivers still do not support ODBC 3 properly but this does not really matter
as the driver manager will translate for your application. If you ask for ODBC 3 behavior
and the driver does not support ODBC 3, the driver manager will return
SQL_SUCCESS_WITH_INFO from SQLDriverConnect and a state of HYC00 (The
driver does not support the version of ODBC behavior that the application requested);
this is not an error.

There are other environment attributes you may set like


SQL_ATTR_CONNECTION_POOLING, SQL_ATTR_CP_MATCH and
SQL_ATTR_OUTPUT_NTS but they are beyond the scope of this introductory tutorial
(see ODBC Reference).

Listing Installed Drivers and Data


Sources
Listing Installed Drivers
When you are using unixODBC you can list installed ODBC drivers using:

odbcinst -q -d

and you can view them in ODBCConfig just as in Windows in the ODBC Administrator.

To programmatically query for drivers you use SQLDrivers like this:

#include <stdio.h>
#include <sql.h>
#include <sqlext.h>

main() {
SQLHENV env;
char driver[256];
char attr[256];
SQLSMALLINT driver_ret;
SQLSMALLINT attr_ret;
SQLUSMALLINT direction;
SQLRETURN ret;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);


SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);

direction = SQL_FETCH_FIRST;
while(SQL_SUCCEEDED(ret = SQLDrivers(env, direction,
driver, sizeof(driver),
&driver_ret,
attr, sizeof(attr), &attr_ret))) {
direction = SQL_FETCH_NEXT;
printf("%s - %s\n", driver, attr);
if (ret == SQL_SUCCESS_WITH_INFO) printf("\tdata truncation\n");
}
}

which when run produces output like this:

OOB - Easysoft ODBC-ODBC Bridge


PostgreSQL - Postgres SQL Driver
EASYSOFT_ISAM - Easysoft Data Access for ISAM

You can use the returned driver name (the driver variable in the above example) when
calling SQLDriverConnect to use DSN-less connections.

Listing Installed Data Sources


When you are using unixODBC you can list installed data sources using:

odbcinst -q -s

and you can view them in ODBCConfig just as in Windows in the ODBC Administrator.

To programmatically query for data sources you use SQLDataSources like this:

#include <stdio.h>
#include <sql.h>
#include <sqlext.h>

main() {
SQLHENV env;
char dsn[256];
char desc[256];
SQLSMALLINT dsn_ret;
SQLSMALLINT desc_ret;
SQLUSMALLINT direction;
SQLRETURN ret;

SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);


SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
direction = SQL_FETCH_FIRST;
while(SQL_SUCCEEDED(ret = SQLDataSources(env, direction,
dsn, sizeof(dsn), &dsn_ret,
desc, sizeof(desc),
&desc_ret))) {
direction = SQL_FETCH_NEXT;
printf("%s - %s\n", dsn, desc);
if (ret == SQL_SUCCESS_WITH_INFO) printf("\tdata truncation\n");
}
}

which when run produces output like this:

mydsn - OOB
pdsn - Postgres
sample_isam - EASYSOFT_ISAM

You can use the returned DSN name (the dsn variable in the above example) when
calling SQLDriverConnect to connect to that DSN.

Connecting to a Driver or Data Source


Once you have a connection handle you can connect to your ODBC driver or data source
using SQLDriverConnect (ODBC 2 applications use SQLConnect but this is much less
flexible). Connecting to your ODBC driver is perhaps one of the largest subjects in
ODBC as SQLDriverConnect can be called in many different ways; this introductory
tutorial will cover the simplest case of connecting to a named data source you have
already created and further tutorials will expand on this.

SQLDriverConnect
The SQLDriverConnect API is:

SQLRETURN SQLDriverConnect(
SQLHDBC ConnectionHandle,
SQLHWND WindowHandle,
SQLCHAR *InConnectionString,
SQLSMALLINT StringLength1,
SQLCHAR *OutConnectionString,
SQLSMALLINT BufferLength,
SQLSMALLINT *StringLength2Ptr,
SQLUSMALLINT DriverCompletion)

Rather than explain every argument in detail (there are plenty of references which do
this) we'll provide the two simplest ways of using SQLDriverConnect. The most
important arguments are ConnectionHandle, InConnectionString, StringLength1 and
DriverCompletion as these are the minimum required to connect to a data source.

• ConnectionHandle is a previously allocated connection handle.


• WindowHandle (if specified) is provided for the driver to enable it to display a
dialog (if necessary). You can specific this as NULL to avoid the driver throwing
a dialog but also see DriverCompletion. On unix where you do not necessarily
have anything like a window handle in MS Windows, specify a non-zero value if
you want dialogs displayed.
• InConnectionString and StringLength1 describe the input connection string. This
is a list of attribute and value pairs separated by semicolons e.g.
"DSN=mydatasource;UID=me;PWD=mypassword;"
More of this below.
• OutConnectionString, BufferLength and StringLength2Ptr are for the driver to
return a full connection string you can use again in connection calls. See String
arguments. More of this below.
• DriverCompletion specifies how the driver will handle the input connection string
especially if there are insufficient attributes defined to connect. There are four
possible settings for DriverCompletion:
o SQL_DRIVER_PROMPT

The driver displays a dialog box, using the values from the connection
string and system information (if any) as initial values. When the user
exits the dialog box, the driver connects to the data source. It also
constructs a connection string from the value of the DSN or DRIVER
keyword in InConnectionString and the information returned from the
dialog box. It places this connection string in the OutConnectionString
buffer.

o SQL_DRIVER_COMPLETE and
SQL_DRIVER_COMPLETE_REQUIRED

If the connection string contains enough information and that information


is correct, the driver connects to the data source and copies
InConnectionString to OutConnectionString. If any information is missing
or incorrect the driver takes the same actions as it does for
SQL_DRIVER_PROMPT, except that if DriverComplete is
SQL_DRIVER_COMPLETE_REQUIRED, the driver disables the
controls for any information not required to connect to the data source.

o SQL_DRIVER_NOPROMPT

If the connection string contains enough information, the driver connects


to the data source and copies InConnectionString to OutConnectionString.
Otherwise, the driver returns SQL_ERROR for SQLDriverConnect. You
would usually use this from non-interactive applications.

• If a dialog box is cancelled SQLDriverConnect should return SQL_NO_DATA.

A Simple Connection
First you must create your named data source (DSN). How you do this depends on the
platform and the driver. In Windows and unix GUI (Graphical User Interface)
environments you start the ODBC Administrator and select your driver where you will be
presented with a dialog you use to name and define the data source. Practically, in unix,
very few drivers come with the ability to create a DSN via a GUI interface and in this
case you can define your data source in a odbc.ini file using an editor (the other tutorials
on this site will help you with this process).

so if you have created a DSN called "mydsn" then the simplest call is:

SQLRETURN ret;
SQLHDBC dbc; /* assume already allocated */

ret = SQLDriverConnect(dbc, NULL, "DSN=mydsn;", SQL_NTS,


NULL, 0, NULL, SQL_DRIVER_COMPLETE);

In this call we have provided the DSN name and asked the driver to complete the
connection string but also we do not want an output connection string or any dialogs
(WindowHandle is NULL). The driver manager will look at the "mydsn" DSN to find out
what driver is required, load the driver and call SQLDriverConnect in the driver with the
same arguments. The driver will look up the mydsn DSN with
SQLGetPrivateProfileString and retrieve all the attributes defined for that data source. So
long as the driver now has enough information to connect to the data source it will.

Format of the ODBC Connection String


The ODBC connection string contains a series of attributes names and values separated
by semi-colons:

connection-string::= empty-string[;] | attribute[;] | attribute;


connection-string
empty-string ::=
attribute ::= attribute-keyword=attribute-value | DRIVER=[{]attribute-
value[}]
attribute-keyword ::= DSN | UID | PWD | driver-defined-attribute-keyword
attribute-value ::= character-string
driver-defined-attribute-keyword = identifier

where character-string has zero or more characters; identifier has one or more characters;
attribute-keyword is not case-sensitive; attribute-value may be case-sensitive; and the
value of the DSN keyword does not consist solely of blanks. Due to the connection string
grammar, keywords and attribute values that contain the characters []{}(),;?*=!@ should
be avoided. The value of the DSN keyword cannot consist only of blanks, and should not
contain leading blanks. Because of the grammar of the system information, keywords and
data source names cannot contain the backslash (\) character. Applications do not have to
add braces around the attribute value after the DRIVER keyword unless the attribute
contains a semicolon (;), in which case the braces are required. If the attribute value that
the driver receives includes the braces, the driver should not remove them, but they
should be part of the returned connection string.

ODBC Defined Connection Attributes


The key to the previous example is the InConnectionString argument which can contain
some ODBC defined attributes (there are also driver defined attributes but these vary per
driver). The ODBC defined attributes are:

• DSN - the name of the data source to connect to.

You must create this before attempting to refer to it. You create new DSNs
through the ODBC Administrator (Windows), ODBCAdmin (unixODBC's GUI
manager) or in the odbc.ini file.

• DRIVER - the name of the driver to connect to. You can use this in DSN-less
connections.
• FILEDSN - the name of a file containing the connection attributes.
• UID/PWD - any username and password the database requires for authentication.
• SAVEFILE - request the DSN attributes are saved in this file.

Returned Connection String


The connection string returned by SQLDriverConnect can be used to reconnect at a later
date. You may ask why bother returning a connection string when one was passed in
which presumably was sufficient to connect in the first place. The reasons for this are:

1. If you use SQL_DRIVER_COMPLETE the driver is free to retrieve any other


attributes it needs to connect in addition to those you passed in.
2. If a dialog was thrown the user may have entered additional or changed values.

A couple of small examples with the Easysoft ODBC-ODBC Bridge (OOB) illustrate
this:

1. Assume you have created a DSN called fred with all the necessary attributes
entered and you call SQLDriverConnect with the connection string "DSN=fred;"
and SQL_DRIVER_COMPLETE. OOB is free to examine the DSN fred and
retrieve the attributes it requires such as ServerPort, TargetDSN, LogonUser and
LogonAuth. The returned connection string will look like
"DSN=fred;ServerPort=myserver:8888;TargetDSN=mydsn;LogonUser=me;Logo
nAuth=password;". Now if the application stores the returned string it can
reconnect without reference to the DSN so the DSN can be changed without
affecting the connection made using the stored string.
2. Assume you have created a DSN called fred but not filled all the fields in; perhaps
because you wanted OOB to throw a dialog requesting the field you omitted. You
call SQLDriverConnect with "DSN=fred;" and SQL_DRIVER_COMPLETE.
OOB will throw its dialog allowing you to enter the missing values and then
return a full connection string which can be used to reconnect later without the
need for user interaction.

Full Connection Example


In A simple connection we only used the minimum number of arguments to
SQLDriverConnect to get connected. This is an example utilising all arguments.

#include <stdio.h>
#include <sql.h>
#include <sqlext.h>

/*
* see Retrieving ODBC Diagnostics
* for a definition of extract_error().
*/
static void extract_error(
char *fn,
SQLHANDLE handle,
SQLSMALLINT type);

main() {
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN ret; /* ODBC API return status */
SQLCHAR outstr[1024];
SQLSMALLINT outstrlen;

/* Allocate an environment handle */


SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
/* We want ODBC 3 support */
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
/* Allocate a connection handle */
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* Connect to the DSN mydsn */
ret = SQLDriverConnect(dbc, NULL, "DSN=fred;", SQL_NTS,
outstr, sizeof(outstr), &outstrlen,
SQL_DRIVER_COMPLETE);
if (SQL_SUCCEEDED(ret)) {
printf("Connected\n");
printf("Returned connection string was:\n\t%s\n", outstr);
if (ret == SQL_SUCCESS_WITH_INFO) {
printf("Driver reported the following diagnostics\n");
extract_error("SQLDriverConnect", dbc, SQL_HANDLE_DBC);
}
SQLDisconnect(dbc); /* disconnect from driver */
} else {
fprintf(stderr, "Failed to connect\n");
extract_error("SQLDriverConnect", dbc, SQL_HANDLE_DBC);
}
/* free up allocated handles */
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
}

Sample output from running this code when "fred" is an OOB data source connecting to
MS SQL Server:

sh-2.05$ ./a.out
Connected
Returned connection string was:
DSN=fred;SERVERPORT=server:8888;TARGETDSN=test;UID=dbuser;PWD=d
bpass;
LOGONUSER=fred;LOGONAUTH=mypass;
Driver reported the following diagnostics

The driver reported the following diagnostics whilst running


SQLDriverConnect

01000:1:5703:[unixODBC][NetConn: 141dbe18][Microsoft][ODBC SQL Server


Driver][SQL Server]
Changed language setting to us_english.
01000:2:5701:[unixODBC][NetConn: 141dbe18][Microsoft][ODBC SQL Server
Driver][SQL Server]
Changed database context to 'easysoft_fred'.

Driver and Driver Manager Information


When you start working with ODBC you will inevitably find differences between ODBC
drivers. ODBC defines a set of informational types you can retrieve which describe the
ODBC driver, driver manager and data source. You use SQLGetInfo to retrieve this
information.

SQLRETURN SQLGetInfo(SQLHDBC ConnectionHandle,


SQLUSMALLINT InfoType,
SQLPOINTER InfoValuePtr,
SQLSMALLINT BufferLength,
SQLSMALLINT StringLengthPtr)

There are basically 3 types of information returned - strings, bitmasks and integer values.
String information types are sometimes binary values represented by 'Y' and 'N'. To
retrieve an informational type, look it up in the ODBC specification to find out what type
it is and then use one of the following:

SQLCHAR string_val[1024];
SQLSMALLINT string_len;
SQLRETURN ret;
SQLUINTEGER u_val;
SQLUSMALLINT su_val;

/* for string values */


ret = SQLGetInfo(dbc,
SQL_XXX, /* e.g. SQL_DATA_SOURCE_NAME */
string_val,
sizeof(string_val),
&string_len);
if (SQL_SUCCEEDED(ret)) {
if (ret == SQL_SUCCESS_WITH_INFO)
printf("buffer too small, string truncated\n");
printf("Returned value is %s\n", string_val);
} else {
/* error */
}

/* for SQLUINTEGER values - mostly bitmasks */


ret = SQLGetInfo(dbc,
SQL_XXX, /* e.g. SQL_INSERT_STATEMENT */
(SQLPOINTER)&u_val,
0, /* ignored for SQLUINTEGER types */
0); /* ignored for SQLUINTEGER types */
if (SQL_SUCCEEDED(ret)) {
printf("Returned value is %lx\n", u_val);
} else {
/* error */
}

/* for SQLUSMALLINT values - mostly counts/limits */


ret = SQLGetInfo(dbc,
SQL_XXX, /* e.g. SQL_MAX_CONCURRENT_ACTIVITIES */
(SQLPOINTER)&su_val,
0, /* ignored for SQLUSMALLINT types */
0); /* ignored for SQLUSMALLINT types */
if (SQL_SUCCEEDED(ret)) {
printf("Returned value is %u\n", su_val);
} else {
/* error */
}

Note Most InfoTypes require the connection handle to be in a connected state since they
are returning information about the driver.

A small example of driver information (without error checking):

#include <stdio.h>
#include <sql.h>
#include <sqlext.h>

main() {
SQLHENV env;
SQLHDBC dbc;
SQLRETURN ret; /* ODBC API return status */
/* Allocate an environment handle */
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
/* We want ODBC 3 support */
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
/* Allocate a connection handle */
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* Connect to the DSN mydsn */
ret = SQLDriverConnect(dbc, NULL, "DSN=web;", SQL_NTS,
NULL, 0, NULL,
SQL_DRIVER_COMPLETE);
if (SQL_SUCCEEDED(ret)) {
SQLCHAR dbms_name[256], dbms_ver[256];
SQLUINTEGER getdata_support;
SQLUSMALLINT max_concur_act;
SQLSMALLINT string_len;

printf("Connected\n");
/*
* Find something out about the driver.
*/
SQLGetInfo(dbc, SQL_DBMS_NAME, (SQLPOINTER)dbms_name,
sizeof(dbms_name), NULL);
SQLGetInfo(dbc, SQL_DBMS_VER, (SQLPOINTER)dbms_ver,
sizeof(dbms_ver), NULL);
SQLGetInfo(dbc, SQL_GETDATA_EXTENSIONS,
(SQLPOINTER)&getdata_support,
0, 0);
SQLGetInfo(dbc, SQL_MAX_CONCURRENT_ACTIVITIES, &max_concur_act, 0,
0);

printf("DBMS Name: %s\n", dbms_name);


printf("DBMS Version: %s\n", dbms_ver);
if (max_concur_act == 0) {
printf("SQL_MAX_CONCURRENT_ACTIVITIES - no limit or undefined\n");
} else {
printf("SQL_MAX_CONCURRNET_ACTIVITIES = %u\n", max_concur_act);
}
if (getdata_support & SQL_GD_ANY_ORDER)
printf("SQLGetData - columns can be retrieved in any order\n");
else
printf("SQLGetData - columns must be retrieved in order\n");
if (getdata_support & SQL_GD_ANY_COLUMN)
printf("SQLGetData - can retrieve columns before last bound
one\n");
else
printf("SQLGetData - columns must be retrieved after last bound
one\n");

SQLDisconnect(dbc); /* disconnect from driver */


} else {
fprintf(stderr, "Failed to connect\n");

}
/* free up allocated handles */
SQLFreeHandle(SQL_HANDLE_DBC, dbc);
SQLFreeHandle(SQL_HANDLE_ENV, env);
}
which to MS SQL Server via OOB produces output like this:

Connected
DBMS Name: Microsoft SQL Server
DBMS Version: 08.00.0760
SQL_MAX_CONCURRNET_ACTIVITIES = 1
SQLGetData - columns must be retrieved in order
SQLGetData - columns must be retrieved after last bound one

Basic Result-set Generating Functions


Result-set generating APIs are the work force of ODBC since they allow you to obtain
metadata and issue SQL querying your database. A result-set is a list of rows and
columns in a statement which you can retrieve e.g. when you call SQLTables to get a list
of tables you will get a result-set with 0 or more rows (one per table or view) and each
row containing columns containing information about the table.

For the meta-data APIs which generate result-sets the ODBC specification states what the
result-set looks like e.g. for SQLTables you get rows of 5 columns called TABLE_CAT,
TABLE_SCHEM, TABLE_NAME, TABLE_TYPE and REMARKS.

The basic meta-data APIs which generate result-sets we'll mention here are:

• SQLTables - returns the list of table, catalog, or schema names, and table types,
stored in the data source. The most basic call returning a result-set containing all
tables and views is:
• SQLTables(stmt_handle,
• NULL, 0, /* no specific catalog */
• NULL, 0, /* no specific schema */
• NULL, 0, /* no specific table */
• NULL, 0) /* no specific type - table or view */

To restrict the result-set to only tables (no views):

SQLTables(stmt_handle,
NULL, 0, /* no specific catalog */
NULL, 0, /* no specific schema */
NULL, 0, /* no specific table */
"TABLE", SQL_NTS) /* only tables, no views */

If you are using ODBC 3.0 catalogname allows search patterns. You also need to
check/set the statement attribute SQL_ATTR_METADATA_ID as this affects
whether the arguments are treated as case-insensitive identifiers or case-sensitive
patterns/literals.
To retrieve a list of all catalogs:

SQLTables(stmt_handle,
SQL_ALL_CATALOGS, SQL_NTS,
"", 0,
"", 0,
NULL, 0);

and similar calls to retrieve a list of all schemas or tables.

• SQLColumns - returns the list of column names in the specified table. You call it
just like SQLTables above.

Other meta-data APIs returning result-sets are SQLTablePrivileges, SQLStatistics,


SQLSpecialColumns, SQLProcedures, SQLProcedureColumns, SQLPrimaryKeys,
SQLForeignKeys, SQLColumnPrivileges and SQLGetTypeInfo.

To issue queries on your database you use SQLPrepare followed by SQLExecute or you
call SQLExecDirect. Here are some examples:

• To select all the columns in the table "mytable":


• SQLExecDirect(stmt_handle, "select * from mytable", SQL_NTS);
• You would generally use SQLExecDirect for one off queries but if you were
wanting to reissue the same query repeatedly with say different search criteria you
can use SQLPrepare/SQLExecute as follows:
• SQLPrepare(stmt_handle,
• "select * from mytable where mycol = ?", SQL_NTS);
• /* bind a parameter with SQLBindParam */
• /* set parameter's value */
• loop {
• SQLExecute(stmt_handle);
• /* get results */
• /* change parameter's value */
• }

Binding parameters is left for later tutorials but mentioned here because it is the
major reason for using SQLPrepare/SQLExecute instead of SQLExecDirect.

Basic Fetching Results


Now we've covered creating handles, connection and basic result-set generating functions
we can actually write a complete program which does something useful i.e. retrieves
some useful information from the database.

The general form for fetching results is:

1. call some result-set generating ODBC API


2. call SQLNumResultCols to find out how many columns are in the result-set.
3. Optionally call SQLDescribeCol or SQLColAttribute to find out meta data for
the columns.
4. call SQLFetch to retrieve a row. If SQLFetch returns SQL_NO_DATA there are
no rows in the result-set.
5. loop through columns calling SQLGetData to retrieve the column data.
6. go back to 4 until SQLFetch returns SQL_NO_DATA.

This small example illustrates the most basic operations; connect to the database, issue a
request and retrieve the results. This example does not attempt to cover all the ODBC
APIs used completely and does not include proper error checking but it is a working
program which we can also use to cover compilation and linking.

#include <stdio.h>
#include <sql.h>
#include <sqlext.h>

main() {
SQLHENV env;
SQLHDBC dbc;
SQLHSTMT stmt;
SQLRETURN ret; /* ODBC API return status */
SQLSMALLINT columns; /* number of columns in result-set */
int row = 0;

/* Allocate an environment handle */


SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
/* We want ODBC 3 support */
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
/* Allocate a connection handle */
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);
/* Connect to the DSN mydsn */
/* You will need to change mydsn to one you have created and tested */
SQLDriverConnect(dbc, NULL, "DSN=mydsn;", SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_COMPLETE);
/* Allocate a statement handle */
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
/* Retrieve a list of tables */
SQLTables(stmt, NULL, 0, NULL, 0, NULL, 0, "TABLE", SQL_NTS);
/* How many columns are there */
SQLNumResultCols(stmt, &columns);
/* Loop through the rows in the result-set */
while (SQL_SUCCEEDED(ret = SQLFetch(stmt))) {
SQLUSMALLINT i;
printf("Row %d\n", row++);
/* Loop through the columns */
for (i = 1; i <= columns; i++) {
SQLINTEGER indicator;
char buf[512];
/* retrieve column data as a string */
ret = SQLGetData(stmt, i, SQL_C_CHAR,
buf, sizeof(buf), &indicator);
if (SQL_SUCCEEDED(ret)) {
/* Handle null columns */
if (indicator == SQL_NULL_DATA) strcpy(buf, "NULL");
printf(" Column %u : %s\n", i, buf);
}
}
}

To compile this code using the unixODBC driver manager installed in /usr/local you
would use:

cc -I/usr/local/include exam1.c -o exam1 \


-L/usr/local/lib -lodbc

When run it produces output like:

Row 0
Column 1 : easysoft
Column 2 : dbo
Column 3 : activeproductcodes
Column 4 : TABLE
Column 5 : NULL
Row 1
Column 1 : easysoft
Column 2 : dbo
Column 3 : products
Column 4 : TABLE
Column 5 : NULL

For more about fetching results, see ODBC from C Tutorial Part 2 - Fetching Results

Handling and Reporting ODBC Errors


and Diagnostics
Return Statuses
All ODBC APIs return a status value which may be used to check whether the function
succeeded or not.

In C you can test the return value from an ODBC function using the macro
SQL_SUCCEEDED e.g.
SQLRETURN fsts;
/* Assume for this example the environment has already been allocated */
SQLHENV envh;
SQLHDBC dbch;

fsts = SQLAllocHandle(SQL_HANDLE_DBC, envh, &dbch);


if (!SQL_SUCCEEDED(fsts))
{
/* an error occurred allocating the database handle */
}
else
{
/* Database handle allocated OK */
}

The macro SQL_SUCCEEDED is defined as:

#define SQL_SUCCEEDED(rc) (((rc)&(~1))==0)

Virtually all ODBC functions can return two values which indicate success

• SQL_SUCCESS
• SQL_SUCCESS_WITH_INFO

Both of these returns cause the SQL_SUCCEEDED macro to result in 1. If a function


returns SQL_SUCCESS_WITH_INFO it means that the call succeeded but an
informational message was produced.

e.g. with some drivers you might set the cursor type, prepare a statement and then execute
it. When SQLExecute is called the statement is acted upon but the driver might change
the cursor type to something else. In this case, SQLExecute would return
SQL_SUCCESS_WITH_INFO and the driver would add a diagnostic indicating the
cursor type had been changed.

Another example is SQLGetData which can return SQL_SUCCESS_WITH_INFO to


indicate the buffer you supplied for the column data was not big enough and the data
returned has been truncated.

You should note that a few ODBC functions return a status which fails the
SQL_SUCCEEDED macro but do not indicate an error as such. e.g. SQLFetch can return
SQL_NO_DATA indicating there is no further rows in the result set, this is not
necessarily an error.

Retrieving ODBC Diagnostics


When an ODBC function returns an error or SQL_SUCCESS_WITH_INFO then the
driver will associate a diagnostic with the handle used in the ODBC call. You can obtain
the diagnostic to find out what failed by calling SQLGetDiagRec with the handle you
used in the ODBC call that failed.

The driver may associate multiple diagnostic records with a handle. You can call
SQLGetDiagField and request the SQL_DIAG_NUMBER attribute to find out how
many diagnostics exist. Alternatively, as diagnostic records start at 1, you can repeatedly
call SQLGetDiagRec asking for record 1, then 2 (and so on) until SQLGetDiagRec returns
SQL_NO_DATA.

As, an example, the following C function takes a function name string, handle type and
handle and retrieves all the diagnostics associated with that handle.

void extract_error(
char *fn,
SQLHANDLE handle,
SQLSMALLINT type)
{
SQLINTEGER i = 0;
SQLINTEGER native;
SQLCHAR state[ 7 ];
SQLCHAR text[256];
SQLSMALLINT len;
SQLRETURN ret;

fprintf(stderr,
"\n"
"The driver reported the following diagnostics whilst
running "
"%s\n\n",
fn);

do
{
ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,
sizeof(text), &len );
if (SQL_SUCCEEDED(ret))
printf("%s:%ld:%ld:%s\n", state, i, native, text);
}
while( ret == SQL_SUCCESS );
}

Using the example above which attempts to allocate a database handle you could use
extract_error as follows:

SQLRETURN fsts;
/* Assume for this example the environment has already been allocated */
SQLHENV envh;
SQLHDBC dbch;

fsts = SQLAllocHandle(SQL_HANDLE_DBC, envh, &dbch);


if (!SQL_SUCCEEDED(fsts))
{
extract_error("SQLAllocHandle for dbc", envh, SQL_HANDLE_ENV);
exit(1);
}
else
{
/* Database handle allocated OK */
}

ODBC 2.0 applications will use SQLError instead of SQLGetDiagRec.

Diagnostic Fields
When you call SQLGetDiagRec you can retrieve 3 diagnostic fields:

• State
• Native error code
• Message text

The state is a five character SQLSTATE code. The first two characters indicate the class
and the next three indicate the subclass. SQLSTATEs provide detailed information about
the cause of a warning or error. You can look states up in the ODBC specification.

The native error code is a code specific to the data source. This number is often
extremely useful to the driver developers in locating an internal error or state. If you are
reporting a bug in the OOB ODBC driver for which you obtained an error you should
always quote the ODBC function called, the error text and this native number.

The message text is the text of the diagnostic. This string takes one of two forms:

For errors and warnings that do not occur in a data source the format is:

[vendor-identifier][ODBC-component-identifier]component-supplied-text

otherwise it is:

[vendor-identifier][ODBC-component-identifier][data-source-identifer]
data-source-supplied-text

***********************************
**********************************
***
PAGE 2:
I'm not sure if a tutorial on ADO database functions is really needed. It is better
documented for use under .NET or in the MFC structure. I'm not much of a fan of either
of these so I found a way to use it without getting into too much of the Microsoft
stuff. The code examples given here and the attached sample program was built and
tested under MS Visual Studio 6, I haven't tried it under any other compilers but it ought
to work there as long as you have the Windows API libraries available.

To begin you need to include the header file stdafx.h. You also will need to connect to
the msado15.dll library. The easiest way to do that is with an #import statement, like
this:

#import "c:\program files\common files\system\ado\msado15.dll" rename("EOF",


"EOFile")

The reason for the rename() during the #import is so that any existing EOF markers are
not overwritten. The next thing needed is a variable that will initialize and un-initialize
the OLE libraries. You ought to be able to call the functions directly, I usethe variable so
I can't get them out of place or forget to cleanup. Here is the structure I use for this
purpose:

struct StartOLEProcess
{
StartOLEProcess()
{
::CoInitialize(NULL);
}
~StartOLEProcess()
{
::CoUninitialize();
}
} _start_StartOLEProcess;

Now that everything is setup, lets get into the function that needs to work on the
database. We will need several variables to connect to the database. First is a variable
used as a connection to the actual database, very similar to a FILE * variable when
working with a file. We will also need a recordset variable to store any data retrieved
from the database. Now the recordset holds the data we got from the database, we can't
access the data directly from the recordset. To actually read the database we need a
variable for each field in the database. When we work on a database ADO can give us
information about what records were affected by our actions, this goes a little beyond the
scope of this tutorial but we need a variable for this to satisfy some function calls. And
finally don't forget the variable used to initialize OLE. Here is the declarations of these
variables:

/*The database connection variable*/


ADODB::_ConnectionPtr Con = NULL;
/*The recordset variable*/
ADODB::_RecordsetPtr RecSet = NULL;
/*A single field pointer, you will probably need multiple for real database work*/
ADODB::FieldPtr Field;
/*Variable to get the affected records*/
VARIANT *RecordsAffected = NULL;
/*The important OLE variable*/
StartOLEProcess OLEVar;

You may want to bundle all those variables into the StartOLEProcess structure to keep
things organized, but that is up to you. The first thing we need to do is initialize the
connection variable. We do this by calling its member function CreateInstance(). This
function will return 0 if it is successful. This is the only function that can be error tested
this way, all other function calls use the try/catch method of error detection, I won't
include the error detection for them to keep the code samples shorter.

if (Con.CreateInstance(__uuidof(ADODB::Connection), NULL) != 0)
{
printf("Couldn't create the connection variable");
}

Assuming all went well there we can try to connect to the database. To do this we need
to create a connection string and call the Open() member function of the connection
pointer. The connection string describes where the database is located and what type of
database it is. ADO has the ability to connect to many types of databases and each type
uses a different connection string. For an example connection I will use a standard
Microsoft Access database. For that our connection string looks like this
"Provider=Microsoft.Jet.OLEDB.4.0; Data Source=C:\Database.mdb", assuming the
database file is C:\Database.mdb. The Open() function takes 4 parameters, the first is the
connection string, the second is a user name, the third is a password, and the fourth is an
options variable that selects the access necessary to get the default read/write access use
0. The username and password are suplied to the database if the database is encrypted or
requires certain permissions to be accessed. Take notice here that the Open() function is
accessed as if the connection varialbe was apointer, unlike the CreateInstance()
funciton. This is because the connection variable we created is a pointer, the
CreateInstance() function created a true connection variable for it to point to, there isn't
any way I have found to directly create a connection variable you always have to go
through a pointer like this. Here is how that would look:

try
{
Con->Open("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=C:\\Database.mdb",
"", "", 0);
}

catch (int Exception)


{
/*I know I said I wouldn't show the try/catch but I felt I should show it at least once*/
printf("Unable to connect to the database");
}

Now that we have the database opened the easiest way to interact with the database is
through SQL statements. I won't to go into SQL since there are a lot of good tutorials
already available, the statements I use most often are SELECT to retieve data from the
database, INSERT to add records to the database, and UPDATE to modify a record in the
database. You can use the Execute() member function of the connection variable to pass
a SQL statement to the database. The Execute() function takes three parameters, the first
is the SQL statement as a NULL terminated string, the second is the records affected
variable, the third value is for options I use 0 again to get the defaults. The Execute()
function will throw an error if there is a problem processing the SQL statement so be sure
that you catch the exceptions from this function. The Execute() function will return a
recordset when a SELECT statement is passed. In this way you can retrieve data from
the database, to do this just put the return into the recordset variable we created
before. Here is how that looks:

RecSet = Con->Execute("SELECT * FROM Table1", RecordsAffected, 1);

As I stated earlier you can't get the data directly from the recordset you need to have
additional variables for each field you want the data from. You set the field variables to
show the data from a specific field that was returned by the select statement. The field
variables will only give the value of that one field from the current record selected by the
recordset variable. You can use the MoveNext() and MovePrevious() member functions
of the recordset variable to navigate through all the returned records. The field variables
will advance through the records with the recordset variable, you don't need to do
anything special to have them update. The recordset variables have a EOFile member
(normally is EOF, but it was renamed during the #import) if this value is not 0, then the
end of the recordset has been reached. A simple while loop can be used to scan through
all the records in your recordset. Here is how that would look:

/*Link the Field variable to the field named "Field1"*/


Field = RecSet->Fields->GetItem("Field1");

while (!RecSet->EOFile)
{
/*Access the data from the Field variable*/
RecSet->MoveNext();
}

That comment in the while loop is hiding some fairly important code. The reason for that
is because each field in a database can be a different type of data, and each datatype will
need to be handled slightly differently. Generally speaking there are three types of data
in a database, strings, numbers, and dates. Each one has a subset of data in it, but
programatically you can handle them in general terms. To determine what type of data
the field is check the Type member of the field variable. This will be an integer value
that gives the type of data in this field, unfortunately I don't have a list of all the possible
values but I can give a few of them from Access databases:
7 Boolean
11 Boolean
2 Integer
202 String
203 String
To find the type number of various field types I created a database with several field
types and pulled a recordset for all of the fields then compared the type numbers with the
field type in the database. I know its boring and tedious, but I don't know of any other
way of retrieving accurate field types. Inside of each field variable there is a member
called Value that is of type _variant_t which can be used to get the actual field data. In
Value only the appropriate value will be filled in, for example if the database field type is
an integer the Value member won't give the value as a string, only as an integer. There
may be a way to perform that conversion but I'm not familiar enough with the _variant_t
variable type to do that. Integer values can be accessed straing out of the Value member,
strings through need to be converted into the standard NULL terminated array. To do
that you can use the function WideCharToMultiByte(), this function takes 8
parameters. Only the third, fifth, and sixth paramters are really needed, the rest can
always be the same. The third parameter is the string to be converted, the fifth parameter
is a pointer to a buffer to store the converted string, and the sixth is the size of the
buffer. Here are a few if statements that can be used to handle the data from the field
variable:

if ((Field->Type == 202)||(Field->Type == 203))


{/*Handling a string database field type*/
char String[100];
WideCharToMultiByte(CP_ACP, 0, Field.bstrVal, -1, String, 100, NULL, NULL);
printf("Data: %s\n", String);
}

if ((Field->Type == 7)||(Field->Type == 11))


{/*Handling a boolean database field type*/
printf("Data: %d\n", Field->Value.boolVal);
}

if (Field->Type == 2)
{/*Handling a integer database field type*/
printf("Data: %d\n", Field->Value.intVal);
}

Once you have done all the work you needed with the recordset and the database you
have to do a little cleanup. The recordsets need to be closed as well as teh database
connection, both have convenient member functions called Close(). Here is how they
look:

RecSet->Close();
Con->Close();

This by no means covers all of the aspects of ADO, for that matter it doesn't even cover
all of the aspects of the functions I mentioned. There are a lot of options you can set in
the connection string when connecting to a database, and dozens of additional database
field types that will need to be dealt with. The idea of this article was only to present the
background and basics for working with ADO database connections. Hopefully that
much at least was covered.

DBtest.cpp
#include<stdio.h>
#include"C:\Program Files\Microsoft Visual Studio\VC98\mfc\SRC\stdafx.h"

#import "c:\program files\common files\system\ado\msado15.dll" rename("EOF",


"EOFile")

struct StartOLEProcess
{
StartOLEProcess()
{
::CoInitialize(NULL);
}
~StartOLEProcess()
{
::CoUninitialize();
}
} _start_StartOLEProcess;
void main(void)
{
ADODB::_ConnectionPtr con = NULL;
ADODB::_RecordsetPtr rec = NULL;
ADODB::FieldPtr pAuthor;
_variant_t vAuthor;
char sAuthor[40];
HRESULT hr = S_OK;
char File[255], ConStr[500];
VARIANT *vRecordsAffected = NULL;
int ctr;

printf("\nEnter Database Path and File name: ");


fgets(File, 250, stdin);
for (ctr = 0; (unsigned int)ctr < strlen(File); ctr++)
{
if (File[ctr] == '\n')
{
File[ctr] = '\0';
break;
}
}
ConStr[0] = '\0';
strcat(ConStr, "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=");
strcat(ConStr, File);

hr = con.CreateInstance(__uuidof(ADODB::Connection));
printf("\nCreateInstance result= %d uuidof= %d\n", hr,
__uuidof(ADODB::Connection));
printf("\nConnection object created.");

con->Open(ConStr, "", "", 0);

printf("\nConnection opened.");

printf("\nEnter Database table name: ");


fgets(File, 250, stdin);
for (ctr = 0; (unsigned int)ctr < strlen(File); ctr++)
{
if (File[ctr] == '\n')
{
File[ctr] = '\0';
break;
}
}
ConStr[0] = '\0';
strcat(ConStr, "SELECT * FROM ");
strcat(ConStr, File);
rec = con->Execute(ConStr, vRecordsAffected, 1);

printf("\nSQL statement processed");

printf("\nEnter Database field name: ");


fgets(File, 250, stdin);
for (ctr = 0; (unsigned int)ctr < strlen(File); ctr++)
{
if (File[ctr] == '\n')
{
File[ctr] = '\0';
break;
}
}
pAuthor = rec->Fields->GetItem(File);

if ((pAuthor->Type == 202)||(pAuthor->Type == 203))


{
printf("\nGetting data now...\n");

while (!rec->EOFile)
{
vAuthor.Clear();
vAuthor = pAuthor->Value;
WideCharToMultiByte(CP_ACP, 0, vAuthor.bstrVal, -1, sAuthor, sizeof(sAuthor),
NULL, NULL);

printf("\n%s", sAuthor);
rec->MoveNext();
}
printf("\n\nEnd of data.");
}
else
{
if ((pAuthor->Type == 11)||(pAuthor->Type == 7))
{
printf("\nGetting data now...\n");

while (!rec->EOFile)
{
vAuthor.Clear();
vAuthor = pAuthor->Value;

printf("\n%d", vAuthor.boolVal);
rec->MoveNext();
}
printf("\n\nEnd of data.");
}
else
{
if (pAuthor->Type == 2)
{
printf("\nGetting data now...\n");

while (!rec->EOFile)
{
vAuthor.Clear();
vAuthor = pAuthor->Value;

printf("\n%d", vAuthor.intVal);
rec->MoveNext();
}
printf("\n\nEnd of data.");
}
else
{
printf("\nUnable to handle that data type, %d", pAuthor->Type);
}
}
}

rec->Close();
rec = NULL;

printf("\nClosed and removed recordset object.");

con->Close();
con = NULL;

printf("\nClosed and removed connection object.");

return;
}
***********************************************************************
***********************************************************************
***********************************************************************
***

S-ar putea să vă placă și