|GNADE User's Guide: GNADE, The GNat Ada Database Environment; Version 1.5.3; Document Revision $Revision: 1.45 $|
The ODBC API typically maintains a set of resources on behalf of the calling application, such as an ODBC Environment, Connections, Statements etc. All those resources have attributes that can be set or get by an application. These attributes have different data types.
As a rather low level API ODBC is oriented to wards low level languages like C. For the above mentioned access to the attributes of various resources the API implements calls in such a way that you have to specify a pointer to a chunk of memory and a parameter containing the length of this area in bytes and then the API fills the area of memory with data or reads data from the area. It's up to the caller to make sure that the so described memory area contains valid data of a type expected by the call. A "C" language prototype of a typical call of this category looks like this:
SQLRETURN SQLGetConnectAttr( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER BufferLength, SQLINTEGER *StringLength); SQLRETURN SQLSetConnectAttr( SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER Value, SQLINTEGER StringLength);
The parameter "Attribute" is actually an enumeration. An integer number denotes the attribute you're interested in. Different attributes have different data types and there is no rule for the mapping of attributes to their type. You have to read the documentation!
We think this is not the level of type safety we should provide to Ada95 clients of this API. We therefore implemented the following scheme to deal with this mapping problem. We will not describe the internals of this scheme here, but how to use it in your application.
The core of the mapping mechanism is the generic package GNU.DB.SQLCLI.Dispatch which you never will instantiate directly. Lets for example take the connection attributes of the ODBC API to demonstrate the use. You'll find the connection attribute handling in the package GNU.DB.SQLCLI.Connection_Attribute. What you find there is an enumeration type named SQL_CONNECTION_ATTRIBUTE. This type represents the plain SQLINTEGER parameter of the above mentioned C API call. In this package you'll find these instantiations:
package Connection_Attributes is new GNU.DB.SQLCLI.Generic_Attr (Context => SQLHDBC, T => SQL_CONNECTION_ATTRIBUTE, Base => SQLINTEGER, Get => Get_Connect_Attr, Set => Set_Connect_Attr, Default_Context => Null_Handle); subtype Connection_Attribute is Connection_Attributes.Attribute_Value_Pair; package Dispatch is new GNU.DB.SQLCLI (Connection_Attribute);The generic package GNU.DB.SQLCLI.Generic_Attr defines an abstract tagged type Attribute_Value_Pair. This type has a single component: "Attribute", which is of the enumeration type to be mapped (formal parameter T in the above instantiation). There exist derived types from this abstract type for the various data types that are possible as attributes (bitmap, boolean, boolean_string, context, enumerated, integer, pointer, string, unsigned). All these derived types add one additional component to the abstract base type: "Value" whose type is selected according to the needs of the attribute to be mapped.
The dispatch package has the instantiation of the generic as parameter and does set up internally all mappings necessary to return a correctly typed Attribute_Value_Pair'Class for an attribute enumeration value. The C API calls now translate into these Ada95 calls:
function SQLGetConnectAttr (ConnectionHandle : SQLHDBC; Attribute : SQL_CONNECTION_ATTRIBUTE; MaxLength : SQLSMALLINT := SQL_MAX_OPTION_STRING_LENGTH) return Connection_Attribute'Class; procedure SQLSetConnectAttr (ConnectionHandle : in SQLHDBC; AttrRec : in Connection_Attribute'Class);If you look into the package GNU.DB.SQLCLI.Connection_Attribute you for example find there this definition
type ACCESS_MODE is (SQL_MODE_READ_WRITE, SQL_MODE_READ_ONLY); for ACCESS_MODE'Size use SQLINTEGER'Size; SQL_MODE_DEFAULT : constant ACCESS_MODE := SQL_MODE_READ_WRITE; package Dsp_Access_Mode is new Dispatch.A_Enumerated (SQL_ATTR_ACCESS_MODE, ACCESS_MODE, SQLINTEGER, "ACCESS_MODE"); subtype Connection_Attribute_Mode is Dsp_Access_Mode.Info;From this you can see that the connection attribute SQL_ATTR_ACCESS_MODE is mapped to an enumerated type ACCESS_MODE. So a call to set the access mode looks like this:
SQLSetConnectAttr (connHandle, Connection_Attribute_Mode'( Attribute => SQL_ATTR_ACCESS_MODE, Value => SQL_MODE_READ_ONLY) );and a call to get the attribute may look like this:
declare attr : Connection_Attribute_Mode; begin attr := Connection_Attribute_Mode( SQLGetConnectAttr (connHandle, SQL_ATTR_ACCESS_MODE) ); end;Note that the type conversion is required to do the dynamic type check of the function return which returns a Connection_Attribute'Class value.
You'll find this technique in these packages:
Due to the dynamic type checking implemented for the attribute handling, all calls dealing with attributes will cost some more cycles than a direct call to the plain C API. All other ODBC calls are a very thin layer around the C API. As attribute set/get calls are rare compared to queries etc. this is acceptable. But it explains while a - in theory - thin binding is compiled into a rather huge library. This is because all the type mapping information is compiled into the library.