5.7. Objects and Interfaces Implemented by the OpenLink Provider

The OpenLink provider implements four OLE DB objects.

  • The data source object, which enables consumers to connect and initialize the interaction with the database.

  • The session, which enables consumers to create a rowset for the data set in the database.

  • The rowset, which exposes a data set to the consumer.

  • The command, which enables consumers to create a SQL string for the data in the database.

These objects, along with the interfaces implemented in the OpenLink provider, are described below.

Table 5.2. OLE-DB Interfaces Implemented

Interface name Method name

IDBInitialize

Initialize

Uninitialize

IDBCreateSession

CreateSession

IDBProperties

GetProperties

GetPropertyInfo

SetProperties

IPersist

GetClassID

IGetDataSource

GetDataSource

ICommand

Cancel

Execute

GetDBSession

ICommandText

GetCommandText

SetCommandText

ICommandProperties

GetProperties

SetProperties

ICommandPrepare

Prepare

Unprepare

IOpenRowset

OpenRowset

IColumnsInfo

GetColumnInfo

MapColumnIDs

IConvertType

CanConvert

IAccessor

AddRefAccessor

CreateAccessor

GetBindings

ReleaseAccessor

IRowset

AddRefRows

GetData

GetNextRows

ReleaseRows

RestartPosition

IRowsetInfo

GetProperties

GetReferencedRowset

GetSpecification

IRowsetChange

DeleteRows

InsertRow

SetData

IRowsetLocate

Compare

GetRowsAt

GetRowsByBookmark

Hash

ISessionProperties

GetProperties

SetProperties

ISupportErrorInfo

InterfaceSupportsErrorInfo

IErrorInfo

GetDescription

GetGUID

GetHelpContext

GetHelpFile

GetSource

IErrorLookup

GetErrorDescription

GetHelpInfo

ReleaseErrors

ISQLErrorInfo

GetSQLInfo


5.7.1. Invoking the OpenLink Provider

The OpenLink ODBC Provider (oploleod.dll) can be loaded using a variety of methods, depending on your programming environment.

Using CoCreateInstance

If you are writing an application which calls the OLE DB API directly, a consumer typically creates a provider's Data Source object by passing the provider's class identifier (CLSID) to the COM CoCreateInstance function and requesting the IDBInitialize interface. Every OLE DB provider declares a unique CLSID for itself. The CLSIDs for the OpenLink ODBC Provider, the OpenLink Error Lookup Service and the OpenLink ODBC Data Source Enumerator are given in the example below, which declares macros for each of the class identifiers.

#ifdef DBINITCONSTANTS
// Provider CLSID
EXTERN_C const CLSID CLSID_OPLOLEDB =
  {0X2D93A18D, 0XAC86, 0X11D1, {0X9C, 0XEA, 0XE5, 0X2A, 0X53, 0XBE, 0XA0, 0X7D}};
// OpenLink Error Lookup Service CLSID
EXTERN_C const CLSID CLSID_OPLOLEDB_ERROR =
  {0X2D93A18E, 0XAC86, 0X11D1, {0X9C, 0XEA, 0XE5, 0X2A, 0X53, 0XBE, 0XA0, 0X7D}};
// OpenLink ODBC Data Source Enumerator CLSID
EXTERN_C const CLSID CLSID_OPLOLEDB_ENUM =
  {0X2D93A190, 0XAC86, 0X11D1, {0X9C, 0XEA, 0XE5, 0X2A, 0X53, 0XBE, 0XA0, 0X7D}};
#else //DBINITCONSTANTS
EXTERN_C const CLSID CLSID_OPLOLEDB;
EXTERN_C const CLSID CLSID_OPLOLEDB_ERROR;
EXTERN_C const CLSID CLSID_OPLOLEDB_ENUM;
#endif //DBINITCONSTANTS
IDBInitialize * pIDBInitialize;
HRESULT hr;
hr = CoCreateInstance(CLSID_OPLOLEDB, NULL, CLSCTX_INPROC_SERVER,
      IID_IDBInitialize, (void**) &pIDBInitialize);
if (FAILED(hr))
{
   // Display error
...
}

5.7.2. Connecting from ADO or .Net

The provider's name is "OpenLinkODBC". The OpenLink OLE DB Provider is invoked from ADO or .Net using a connection string which typically takes the following form:

Provider=OpenLinkODBC; Data Source=w2ks2;User ID=SCOTT;Password=tiger;Extended Properties="Cursors=Driver;BookmarkDefault=On";

5.7.3. Initialization Properties

When the consumer calls IDBInitialize::Initialize, the OpenLink Provider calls the UDBC/ODBC functions SQLSetConnectOption, to set various connection options, and SQLDriverConnect, to connect to an ODBC data source. The values passed to SQLSetConnectOption and SQLDriverConnect come from the initialization properties set by the consumer.

When the data source object is first created, the value of each initialization property is set to a default value.

Initialization Properties Used

The following lists the initialization properties currently used by the OpenLink Provider. Any properties not listed are not used by the OpenLink Provider.

Table 5.3. OLE-DB Initialization Properties

Property Description
DBPROP_AUTH_PASSWORD Passed as the value of the PWD keyword in SQLDriverConnect.
DBPROP_AUTH_USERID Passed as the value of the UID keyword in SQLDriverConnect.
DBPROP_INIT_CATALOG Sets the initial catalog for data sources which recognise catalogs.
DBPROP_INIT_DATASOURCE Passed as the value of the DSN keyword in SQLDriverConnect.

DBPROP_INIT_HWND

Passed as the value of the hwnd argument in SQLDriverConnect.

DBPROP_INIT_MODE

Mapped to the ODBC connect option SQL_ACCESS_MODE.

DBPROP_INIT_PROMPT

Passed as the value of the fDriverCompletion parameter in SQLDriverConnect.
DBPROP_INIT_PROVIDERSTRING Specifies extended properties for controlling ODBC Cursor Library usage (through the 'Cursors' keyword) and the whether bookmarks are exposed by default on rowsets (through the 'BookmarkDefault' keyword).
DBPROP_INIT_TIMEOUT Mapped to the ODBC connect option SQL_LOGIN_TIMEOUT.

Setting and Getting Provider Properties

The data source object is the first object created when a consumer instantiates the provider by calling CoCreateInstance.

The data source object provides the starting point for communications between the provider and consumer. For example, a consumer can call CoCreateInstance and request an IDBInitialize interface pointer to instantiate a data source object. The provider has a CLSID (class ID) that is stored in the Windows Registry. The consumer can use this CLSID with CoCreateInstance to instantiate the data source object. The OpenLink provider setup program registers the OpenLink provider in the Windows Registry.

The data source object is responsible for setting and returning information about the properties supported by the provider and exposing the list of supported keywords and literals. This functionality is supported through the mandatory IDBProperties interface and the optional IDBInfo interface. The IDBProperties interface contains three methods:

  • GetProperties returns the list of properties currently set on the data source object.

  • GetPropertyInfo returns information about supported rowset and data source properties.

  • SetProperties sets the properties on the data source object.

The IDBInfo interface contains two methods:

  • GetKeywords returns a list of supported keywords.

  • GetLiteralInfo returns information about literals used in text commands.

5.7.4. Initializing and Uninitializing the Data Source Object

The IDBInitialize interface contains two methods: Initialize and Uninitialize.

Initialize enables consumers to explicitly initialize a data source object. Consumers must set properties on the data source object before attempting to initialize it; and consumers must supply a valid datasource name to the database in IDBProperties::SetProperties. If the datasource is invalid, the OpenLink provider returns an E_FAIL error on initialization.

Uninitialize enables consumers to return the data source object to an uninitialized state. It is an error to call IDBInitialize::Uninitialize when there are open sessions or rowsets on the data source object.

5.7.5. Creating a Session

After you initialize the data source object, you must create a session object to manage the session and provide the framework needed to create a rowset with IOpenRowset::OpenRowset. The IDBCreateSession::CreateSession interface on the data source object enables you to create a new session object and returns an interface pointer to the session.

Once the session has been created, the provider must expose the interface pointer to the data source object that created the session. This interface pointer is exposed through the mandatory interface IGetDataSource.

5.7.6. Creating a Rowset

The session contains the interface that enables consumers to open a database and create a rowset object containing all rows in the database. The OpenLink provider implements both the IOpenRowset, and the ICommand (and associated ICommandText, ICommandProperties) interfaces on the session to create a rowset..

Instantiating and Exposing a Rowset

The IOpenRowset interface contains a single method: OpenRowset. IOpenRowset is a required interface on the session. IOpenRowset::OpenRowset can be used by consumers that do not support command objects to generate a rowset of all rows in a table or index.

The ICommand interface contains the method: Execute. ICommand::Execute generates a rowset from the SQL query set by ICommandText::SetCommandText.

Consumer and Provider Interactions with the Rowset

After receiving the rowset interface pointer, the consumer can request rowset metadata from the provider through IColumnsInfo. The consumer then creates bindings by requesting IAccessor from the provider and specifying the bindings through IAccessor::CreateAccessor. The provider returns a handle to the accessor to the consumer.

The consumer then requests a number of rows from the provider using IRowset::GetNextRows. The provider retrieves the data for these rows and stores it in the data cache. The provider then returns an array of row handles to the consumer. Each row handle returned by the provider has an initial reference count of one. The consumer is then free to get the data for any rows from the provider using GetData. The consumer supplies GetData with the row handle, the handle of an accessor, and the buffer location into which to return the data; the provider copies the data to the location specified by the consumer.

To update rows, consumers call IRowsetChange::SetData, which sets the data in the data cache to the values specified by the consumer. To delete rows from the rowset, the consumer calls IRowsetChange::DeleteRows. To insert rows into the rowset, the consumer calls IRowsetChange::InsertRow. Note that the OpenLink Provider is not able to fetch back a newly inserted row if the underlying datasource does not provide this functionality.

When the consumer makes any change to data in the data cache, the effects of the change are written to the data source immediately. OLE DB specifies a change-buffering model, which enables the consumer to make changes that are not realized until the consumer calls IRowsetUpdate::Update; this model is not supported by the OpenLink provider.

When the consumer has finished working with a row, it can release the row by calling IRowset::ReleaseRows. ReleaseRows simply decrements the reference count on the row in the data cache. If the reference count for that row reaches zero, the row data is released from the data cache.

5.7.7. Exposing Metadata

the OpenLink Provider expose information about the columns of a rowset through IColumnsInfo. The information for each column is returned in a DBCOLUMNINFO structure. OLE DB also has an optional metadata interface, IColumnsRowset; the OpenLink provider does not implement this interface.

The GetColumnInfo method returns metadata that is most commonly used by consumers: column ID, column name, the ordinal number of the column in the rowset, the column's data type, and so on.

The provider returns the information in an array of DBCOLUMNINFO structures, one DBCOLUMNINFO structure per column in the rowset. The order of the structures returned in the array is the order in which the columns appear in the rowset.

IColumnsInfo

Columns that have an ODBC SQL type of SQL_LONGVARCHAR or SQL_LONGVARBINARY are returned as type DBTYPE_BYTES or DBTYPE_STR, and the DBCOLUMNFLAG_ISLONG is set in the dwFlags element of the DBCOLUMNINFO structure.

Returning Column Ordinals

Columns in a rowset are identified by a column ID, which is a value of type DBID in the DBCOLUMNINFO structure.

The MapColumnIDs method returns column ordinals for all column IDs provided in the rgColumnIDs array. Column ordinals do not change during the life of the rowset, but may change between different instances of the rowset.

5.7.8. Supported Conversions

Before the consumer creates an accessor, it can call IConvertType::CanConvert to determine if the provider supports a particular conversion.

Default Data Type Mapping

The OpenLink Provider binds to the ODBC/UDBC data source using the types in the table below. The SQL type is queried using SQLDescribeCol. The sign of the data type (signed/unsigned) is determined using SQLColAttributes. It is used in deciding which C type to use in internal buffers and which type indicator to return through IColumnsInfo::GetColumnInfo.

Table 5.4. OLE-DB Data Type Mappings

SQL Type Indicator Indicator of C Type Used For Internal Buffers OLE DB Type Indicator

SQL_CHAR,

SQL_VARCHAR,

SQL_LONGVARCHAR,

SQL_DECIMAL,

SQL_NUMERIC

SQL_C_CHAR

DBTYPE_STR

SQL_BIT

SQL_C_BIT

DBTYPE_BOOL

SQL_TINYINT,

SQL_SMALLINT

SQL_C_USHORT,

SQL_C_SSHORT

DBTYPE_I2

SQL_INTEGER

SQL_C_ULONG,

SQL_C_SLONG

DBTYPE_I4

SQL_BIGINT

SQL_C_STR

DBTYPE_STR

SQL_REAL

SQL_C_FLOAT

DBTYPE_R4

SQL_FLOAT,

SQL_DOUBLE

SQL_C_DOUBLE

DBTYPE_R8

SQL_BINARY,

SQL_VARBINARY,

SQL_LONGVARBINARY

SQL_C_BINARY

DBTYPE_BYTES

SQL_DATE

SQL_C_DATE

DBTYPE_DATE

SQL_TIME

SQL_C_TIME

DBTYPE_DATE

SQL_TIMESTAMP

SQL_C_TIMESTAMP

DBTYPE_DATE


Length Binding

For types DBTYPE_UI1, DBTYPE_I2, DBTYPE_I4, DBTYPE_I8, DBTYPE_R4, DBTYPE_R8, DBTYPE_CY, DBTYPE_NUMERIC, the length binding is always set to the fixed size of the destination binding type, rather than the internal source type.

Supported Data Conversions

The follwoing table outlines the supported type conversion implemented in the OpenLink provider. An 'X' means supported, and '-' means not supported.

Table 5.5. OLE-DB Data Type Conversions

  I1 I2 I4 I8 UI1 UI2 UI4 UI8 R4 R8 CY DEC NUM BOOL DATE DBDATE DBTIMESTAMP DBTIME BYTES BSTR STR WSTR DISP UNK GUID

I1

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

I2

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

I4

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

I8

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

UI1

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

UI2

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

UI4

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

UI8

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

R4

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

R8

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

CY

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

DEC

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

NUM

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

-

-

-

X

X

X

X

-

-

-

BOOL

-

-

-

-

-

-

-

-

-

-

-

-

-

X

-

-

-

-

X

X

X

X

-

-

-

DATE

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

X

X

X

X

X

X

X

-

-

-

DBDATE

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

X

-

X

X

X

X

X

-

-

-

DBTIME

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

-

X

X

X

X

X

X

-

-

-

DBTIMESTAMP

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

X

X

X

X

X

X

X

-

-

-

BYTES

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

-

X

X

X

X

X

X

X

X

BSTR

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

-

-

X

STR

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

-

X

X

WSTR

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

X

-

X

X

DISP

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

-

-

-

X

X

-

UNK

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

-

-

-

-

X

-

GUID

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

X

X

X

X

-

-

X


5.7.9. Creating and Using Accessors

Consumers describe the memory structure for their buffers through a process called binding. An accessor is a group of bindings. The OpenLink provider does not currently support reference accessors, which allow the consumer direct access to the rowset's data cache.

Accessors are implemented through IAccessor. Accessors are created with IAccessor::CreateAccessor and released with IAccessor::ReleaseAccessor. IAccessor::GetBindings can be used to determine the bindings in an existing accessor. IAccessor::AddRefAccessor enables the consumer to add a reference count to an existing accessor.

Accessors may not always be validated immediately at the time of creation. Instead, they may be validated at the time the first row is fetched. Errors will be returned at the first attempt to use such an accessor.

Creating an Accessor

CreateAccessor associates a set of bindings with an accessor handle that is used to send data to or fetch data from the rowset's data cache. The OpenLink provider supports only the DBACCESSOR_ROWDATA accessor flag, which specifies that the accessor is to be used for rowset data.

Returning Accessor Bindings

GetBindings returns the bindings in an existing accessor.

Adding a Reference Count to an Existing Accessor

AddRefAccessor adds a reference count to an existing accessor.

Releasing an Accessor

ReleaseAccessor decrements the reference count on an accessor; when the reference count reaches zero, the accessor is released.

5.7.10. Rowset MetaData

IRowsetInfo enables consumers to query the properties of a rowset through IRowsetInfo::GetProperties. Consumers can get an interface pointer to the object that created the rowset by calling IRowsetInfo::GetSpecification.

IRowset provides methods for fetching rows sequentially, exposing data from those rows to consumers, and managing the rows in the rowset. IRowset contains five methods: AddRefRows, GetData, GetNextRows, ReleaseRows, and RestartPosition.

Incrementing the Reference Count on Row Handles

AddRefRows increments the reference count on the row handles supplied by the caller. AddRefRows enables consumers to make multiple references to a row in the data cache.

Populating the Data Cache

IRowset::GetNextRows gets the next sequence of rows from the datasource and places them in the rowset's data cache. When GetNextRows is first called, it starts at the beginning of the rowset. After that, GetNextRows maintains information about its current position so it can proceed forward from that position. The OpenLink provider also provides support for IRowset::RestartPosition, which repositions the next fetch position to the beginning of the rowset.

Retrieving Data from the Data Cache

IRowset::GetData enables consumers to retrieve data from the data cache. GetData uses the bindings in the accessor to determine how the data should be returned and what data should be returned to the consumer's buffer. Then, GetData converts the data in the cache to the type specified in the binding and transfers the converted data to the consumer.

Decrementing the Reference Count on Row Handles

IRowset::ReleaseRows decrements the reference count on the rows specified. A consumer must call ReleaseRows once for each time a row was fetched or each time the row had its reference count incremented by AddRefRow. When the reference count reaches zero, the row is released if the rowset is in immediate update mode.

In providers that implement IRowsetUpdate, rows are released unless there are pending changes on the row. The OpenLink provider does not implement this interface; the OpenLink provider always performs rowset updates in immediate mode, which means that changes are immediately applied to the underlying data source. Therefore, the OpenLink provider does not recognize any changes as pending.

Returning to the First Row of the Rowset

IRowset::RestartPosition moves the next fetch position used by GetNextRows to the first row of the rowset.

Updating Rows

IRowsetChange enables consumers to change the values of columns in a row of data. If the consumer wants to change the data, it must first construct an accessor for the columns to be changed. IRowsetChange contains three methods: DeleteRows, InsertRow, and SetData.

Deleting Rows

IRowsetChange also enables consumers to delete rows from the rowset. IRowsetChange::DeleteRows deletes rows from the rowset and are applied to the data source immediately.

IRowsetChange

IRowsetChange is implemented using the UDBC/ODBC function SQLSetPos. It therefore can be exposed only when the underlying datasource supports SQLSetPos. Newly inserted rows cannot be updated.