Binding to Rowset Columns

Accessors are managed through the rowset interface IAccessor. The client application uses the IAccessor interface to specify a set of column bindings and to associate them with an accessor handle. The handle can be passed into other rowset methods that manipulate individual rows of data. When you specify column bindings and create accessors, consider these questions:
  • How will the column be identified?
  • What type can the client bind it to?
  • How much space will be needed to store the data in the client's buffers?
If you have a DBCOLUMNINFO structure for a column, you can easily transfer that information into a DBBINDING structure, as shown in the following table:
DBCOLUMNINFO Members Mapped to DBBINDING Members
DBCOLUMNINFO Members
DBBINDING Members
iOrdinal
iOrdinal
ulColumnSize
cbMaxLen
wType
wType
bPrecision
bPrecision
bScale
bScale
The value of iOrdinal must be the same in both the DBBINDING and DBCOLUMNINFO structures. However, the other members in a DBBINDING structure do not have to match the corresponding members that are returned in a DBCOLUMINFO structure. For example, the wType value in the DBBINDING structure can have any meaningful DBTYPE because the providers support converting data between types as defined by the OLE DB specification. In addition, the values of the DBBINDING members cbMaxLen, bPrecision, and bScale are more closely related to the wType member of the DBBINDING structure than are the corresponding members of the DBCOLUMNINFO structure.
If the column metadata is known at the time that you are creating the client application, then you do not need to query the data provider. Instead, you can build static DBBINDING structures, which can increase run-time performance. To build static DBINDING structures, you must know how to map data set variable attributes to DBBINDING structure members. The following table explains the mapping:
DBBINGING Structure Members and Corresponding Variable Attributes
DBBINDING Structure Member
Corresponding SAS Variable Attribute
iOrdinal
Set this member to the variable number that you want to bind to. The member should be a value from 1 to N, where N is the number of variables that are defined in the data set. (The value 0 is reserved for the self-bookmark column.)
obValue
obLength
obStatus
These three members determine the offset of the column value, its length, and its status of coercion in the buffer that you pass to the provider when you read and update. If the corresponding bit is not set in the dwPart member, these members are ignored.
pTypeInfo
Ignored. Reserved for future use.
pObject
Unsupported. OLE objects cannot be embedded in SAS data sets.
pBindExt
Used for formatting data.
dwPart
This bitmask defines which obValue, obLength, and obStatus members are meaningful. See the OLE DB specification for more information about this field.
dwMemOwner
This member does not map to any SAS variable attributes. You use as described by the OLE DB specification.
eParamIO
Ignored.
cbMaxLen
The number of bytes allocated in your buffer for the data value. This number is determined by the value of wType.
dwFlags
Set this member to 0x00. This value indicates to return regular text from the provider.
wType
A DBTYPE value that indicates the type that is used to represent the data value. This value can directly correspond to the SAS variable's type (DBTYPE_WSTR for character variables; DBTYPE_R8 for numeric variables), or it can be any type that the provider can convert the data to. The OLE DB specification lists the valid conversions between DBTYPEs, and the SAS providers support all of these conversions. At run time you can discover which conversions are allowed by calling the IConvertType::CanConvert() rowset method.
bPrecision
The IOM provider looks only at this member when wType is set to DBTYPE_NUMERIC. In this case, this field defines the maximum precision to use when you get the data. It is ignored when you set and update data.
bScale
Ignored.
In the following example, you open a rowset component named pRowset on a known data set named AUTOS, which contains these variables:
  • MAKE, which is a 40-byte character variable
  • MODEL, a numeric variable
  • MPG, a numeric variable
  • COST, a numeric variable
Bind MAKE as DBTYPE_BSTR and the remaining variables as DBTYPE_STR. This binding means that the first column is a fixed length and the other three columns are varying lengths. So, cbMaxLen and obLength are not considered when you bind MAKE, but they are considered when you bind to MODEL, MPG, and COST.
Put the values of MODEL, MPG, and COST into 15-byte buffers. Buffer your consumer output to place the status and value of MAKE first, followed by the lengths, status, and values of MODEL, MPG and COST. The following code fills in an array of DBBINDING structures to match the described binding and calls the appropriate method to create the OLE DB accessor handle:
IAccessor * pAccessor;
DBBINDING rgBindings[4];
DBBINDSTATUS rgStatus[4];
HACCESSOR hAccessor;
ULONG ulOffset;

 // Lay out each column in memory.
struct COLUMNDATA 
        {
        DWORD dwLength; // length of data returned
        DWORD dwStatus; // status of column
        BYTE bData[1]; // data here and beyond
        };
 // Rounding amount is always a power of two.
#define ROUND_UP( Size, Amount ) (((DWORD)(Size) + ((Amount) - 1)) & ~((Amount) - 1))

// Alignment for placement of each column within memory.
// Rule of thumb is "natural" boundary, such as a 4-byte member should be
// aligned on address that is multiple of 4.
// Worst case is double or __int64 (8 bytes).
#define COLUMN_ALIGNVAL 8

memset( rgBindings, 0, sizeof( rgBindings ) ); // defensive programming

  // Start laying out data at the beginning of our buffer
ulOffset = 0;
rgBindings[0].iOrdinal = 1;
rgBindings[0].dwPart = DBPART_VALUE | DBPART_STATUS;
rgBindings[0].dwFlags = 0x00;
rgBindings[0].obValue = ulOffset + offsetof( COLUMNDATA, bData );
rgBindings[0].obStatus = ulOffset + offsetof( COLUMNDATA, dwStatus );
rgBindings[0].wType = DBTYPE_BSTR;

  // Account for space taken by actual cell value
ulOffset += sizeof( BSTR ) + offsetof( COLUMNDATA, bData );
ulOffset = ROUND_UP( ulOffset, COLUMN_ALIGNVAL ); // round up for alignment
rgBindings[1].iOrdinal = 2;
rgBindings[1].cbMaxLen = 15;
rgBindings[1].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
rgBindings[1].dwFlags = 0x00;
rgBindings[1].obValue = ulOffset + offsetof( COLUMNDATA, bData );
rgBindings[1].obLength = ulOffset + offsetof( COLUMNDATA, dwLength );
rgBindings[1].obStatus = ulOffset + offsetof( COLUMNDATA, dwStatus );
rgBindings[1].wType = DBTYPE_STR;

  // Account for space taken by actual cell value
ulOffset += rgBindings[1].cbMaxLen + offsetof( COLUMNDATA, bData );
ulOffset = ROUND_UP( ulOffset, COLUMN_ALIGNVAL ); // round up for alignment
rgBindings[2].iOrdinal = 3;
rgBindings[2].cbMaxLen = 15;
rgBindings[2].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
rgBindings[2].dwFlags = 0x00;
rgBindings[2].obValue = ulOffset + offsetof( COLUMNDATA, bData );
rgBindings[2].obLength = ulOffset + offsetof( COLUMNDATA, dwLength );
rgBindings[2].obStatus = ulOffset + offsetof( COLUMNDATA, dwStatus );
rgBindings[2].wType = DBTYPE_STR;
 
  // Account for space taken by actual cell value
ulOffset += rgBindings[2].cbMaxLen + offsetof( COLUMNDATA, bData );
ulOffset = ROUND_UP( ulOffset, COLUMN_ALIGNVAL ); // round up for alignment
rgBindings[3].iOrdinal = 4;
rgBindings[3].cbMaxLen = 15;
rgBindings[3].dwPart = DBPART_VALUE | DBPART_STATUS | DBPART_LENGTH;
rgBindings[3].dwFlags = 0x00;
rgBindings[3].obValue = ulOffset + offsetof( COLUMNDATA, bData );
rgBindings[3].obLength = ulOffset + offsetof( COLUMNDATA, dwLength );
rgBindings[3].obStatus = ulOffset + offsetof( COLUMNDATA, dwStatus );
rgBindings[3].wType = DBTYPE_STR;

  // Account for space taken by actual cell value
ulOffset += rgBindings[3].cbMaxLen + offsetof( COLUMNDATA, bData );
ulOffset = ROUND_UP( ulOffset, COLUMN_ALIGNVAL ); // round up for alignment
pRowset->QueryInterface( IID_IAccessor, &hAccessor );
pAccessor->CreateAccessor( DBACCESSOR_ROWDATA, // we are binding to columns in a rowset
                           4, // binding 4 columns
                           rgBindings,
                           0, // ignored for rowset bindings
                           &hAccessor,
                           rgStatus );
 // If the above call fails you can look at rgStatus to identify which column's binding info
 // was invalid. When the call succeeds, hAccessor is a valid accessor handle which can be
 // used as input to IRowset::GetData(), IRowsetChange::SetData() and IRowsetChange::InsertRow()
 // When you are done with the accessor handle, release it.
pAccessor->ReleaseAccessor( hAccessor, 0 );
pAccessor->Release();