//-------------------------------------------------------------------- // Microsoft OLE DB // (C) Copyright 1991 - 1999 Microsoft Corporation. All Rights Reserved. // // @doc // // @module FILEIO.CPP | This module contains the File Manipulation code // for a Comma Seperated Value (CSV) Simple Provider. // // #include "headers.h" #include "fileio.h" static const int ARRAY_INIT_SIZE = 1000; // Data Types supported static const int TYPE_CHAR = 1; static const int TYPE_SLONG = 3; // Data Type Parse strings and lengths static const char CHAR_STRING[] = "CHAR"; static const int CHAR_STRING_SIZE = 4; static const char SLONG_STRING[] = "SLONG"; static const int SLONG_STRING_SIZE = 5; //-------------------------------------------------------------------- // @mfunc Constructor for this class // // @rdesc NONE // CFileIO::CFileIO() { memset(&m_rgpColumnData, 0, sizeof(PCOLUMNDATA) * MAX_COLUMNS); m_pColNames = NULL; m_pvInput = NULL; m_ulDataTypeOffset = 0; m_cColumns = 0; m_cRows = 0; m_FileReadOnly = FALSE; m_pbHeap = NULL; m_cbHeapUsed = 0; m_cbRowSize = 0; } //-------------------------------------------------------------------- // @mfunc Destructor for this class // // @rdesc NONE // CFileIO:: ~CFileIO() { // Release buffer for column names (pointed to by m_rgdbcolinfo). if (m_pbHeap) VirtualFree((VOID *) m_pbHeap, 0, MEM_RELEASE ); // Close file if (is_open()) close(); // Delete buffers SAFE_DELETE( m_pColNames ); SAFE_DELETE( m_pvInput ); } //-------------------------------------------------------------------- // @mfunc Initialization routine, opens file specified and creates // buffers // // @rdesc HRESULTs // @flag S_OK | Succeeded // @flag E_FAIL | Failed to Initialize // @flag DB_E_NOTABLE | File marked as read only or didn't exist // HRESULT CFileIO::fInit ( LPSTR ptstrFileName //@parm IN | File Name to Open ) { // Allocate Stream Buffer m_pvInput = new char[MAX_INPUT_BUFFER ]; if (NULL == m_pvInput) return ResultFromScode( E_FAIL ); // Open the File open( ptstrFileName, ios::in | ios::out | ios::_Nocreate);//, filebuf::sh_read || filebuf::sh_write ); if( !is_open() ) { open( ptstrFileName, ios::in | ios::_Nocreate);//, filebuf::sh_read ); if (!is_open()) return ResultFromScode( DB_E_NOTABLE ); m_FileReadOnly = TRUE; } // Obtain the Column Names, Data Types, and Indexes // for each of the rows if (FAILED( GenerateFileInfo())) return ResultFromScode( E_FAIL ); return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Retrieve the Name associated with a particular column. If // names have not been read from the file yet, retrieve those names. // If a name does not exist for a column, fabricate one. // // @rdesc HRESULT // @flag S_OK | Succeeded // @flag E_FAIL | Invalid Column Number // HRESULT CFileIO::GetColumnName ( DBORDINAL cCols, //@parm IN | Column Number LPSTR* pptstrName //@parm OUT | Pointer to Column Name ) { // Column number greater than MAX if (cCols > MAX_COLUMNS) return ResultFromScode( E_FAIL ); // If Column Names have not been retrieved, // then retrieve them into the internal array if (!m_pColNames) { // Save Current Position and move to beginning of file seekg( 0L ); clear(); // Retrieve the column names record getline( m_pvInput, MAX_INPUT_BUFFER ); if (good() && 0 < gcount()) { m_pColNames = new char[gcount() ]; memcpy( m_pColNames, m_pvInput, gcount()); } else { //Invalid Table, first line does not contain //column metadata. return ResultFromScode( E_FAIL ); } ParseColumnNames( m_pColNames ); return ResultFromScode( S_FALSE ); } ASSERT( pptstrName ); // If the column number is in range then return // the pointer if ((0 == cCols) || (m_cColumns < cCols)) return ResultFromScode( E_FAIL ); else { *pptstrName = m_rgpColNames[cCols]; return ResultFromScode( S_OK ); } } //-------------------------------------------------------------------- // @mfunc Tokenize the column names // // @rdesc HRESULT // @flag S_OK | Parsing yielded no Error // HRESULT CFileIO::ParseColumnNames ( LPTSTR ptstrInput ) { LPTSTR pvInput = ptstrInput; ASSERT( pvInput ); // Set first column pointer if ('\0' != *pvInput) { m_rgpColNames[++m_cColumns] = pvInput; } // Null Terminate each column while ('\0' != *pvInput) { // Check for Comma if (0 == strncmp( ",", pvInput, sizeof(char))) { memcpy( pvInput, "", sizeof(char)); if (0 != strncmp( "", (pvInput+1), sizeof(char))) { m_rgpColNames[++m_cColumns] = (pvInput+1); } } pvInput++; } return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Retrieves the columns data characteristics // // @rdesc HRESULT // @flag S_OK | Succeeded // @flag E_FAIL | Invalid Column Number // HRESULT CFileIO::GetDataTypes ( DBORDINAL cCols, //@parm IN | Column number SWORD* pswType, //@parm OUT | Data Type UDWORD* pudwColDef, //@parm OUT | Precision of the column BOOL* pfSigned //@parm OUT | Is the columns signed ) { HRESULT hr; // Column number greater than MAX if (cCols > MAX_COLUMNS) return ResultFromScode( E_FAIL ); // If Data Types have not been retrieved, // then retrieve them into the internal array if (0 == m_ulDataTypeOffset) { seekg( 0L ); clear(); // To retrieve the Column Data Types, we need // to skip the first row and get to the 2nd row getline( m_pvInput, MAX_INPUT_BUFFER ); m_ulDataTypeOffset = tellg(); getline( m_pvInput, MAX_INPUT_BUFFER ); // Check Stream status if (bad() || 0 == gcount()) return ResultFromScode( E_FAIL ); // Parse the datatypes from the stream. hr = ParseDataTypes(); if (FAILED( hr )) return hr; return ResultFromScode( S_FALSE ); } assert( pswType || pudwColDef || pfSigned ); // If the column number is in range then return // the pointer if ((0 == cCols) || (m_cColumns < cCols)) return ResultFromScode( E_FAIL ); else { *pswType = m_rgswColType[cCols]; *pudwColDef = m_rgudwColSize[cCols]; *pfSigned = m_rgfSigned[cCols]; return ResultFromScode( S_OK ); } } //-------------------------------------------------------------------- // @mfunc Tokenize the DataTypes and Lengths // Valid Data Types are CHAR(n), INTEGER, and LONG // // CHAR, SLONG // // @rdesc HRESULT // @flag S_OK | Parsing yielded no Error // HRESULT CFileIO::ParseDataTypes() { DBORDINAL cColumn = 0; LPSTR pVal, pOpen; LPSTR pchNextToken; assert( m_pvInput ); pVal = strtok_s( m_pvInput, ",\0", &pchNextToken); if (NULL == pVal) return ResultFromScode( E_FAIL ); while (NULL != pVal) { cColumn++; if (0 == _strnicmp( pVal, CHAR_STRING, CHAR_STRING_SIZE )) { m_rgswColType[cColumn] = TYPE_CHAR; pOpen = strstr( pVal, "(" ); m_rgudwColSize[cColumn] = atol( ++pOpen ); m_rgfSigned[cColumn] = FALSE; } else if (0 == _strnicmp( pVal, SLONG_STRING, SLONG_STRING_SIZE )) { m_rgswColType[cColumn] = TYPE_SLONG; m_rgudwColSize[cColumn] = 4; m_rgfSigned[cColumn] = TRUE; } else return ResultFromScode( E_FAIL ); pVal = strtok_s( NULL, ",\0", &pchNextToken); } // should have exactly the same number of types as we have columns if (cColumn != m_cColumns) return ResultFromScode( E_FAIL ); return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Obtain the offsets into the file that each row exists at. // Ignore any deleted rows while reading the file. // // @rdesc HRESULT // @flag S_OK | Got the offsets, Column Names and Data Types // @flag E_FAIL | Could not obtain all the necessary info // HRESULT CFileIO::GenerateFileInfo() { ULONG ulDex = 0; ULONG_PTR ulSavePos; // Generate Column Info, if NULL is returned, a problem // was encountered while reading the Column Names. if (S_FALSE != GetColumnName( 0, NULL )) return ResultFromScode( E_FAIL ); // Generate DataType Mapping, if FALSE is returned, a problem // was encountered while reading the DataTypes if (S_FALSE != GetDataTypes( 0, NULL, NULL, NULL )) return ResultFromScode( E_FAIL ); // Create and Initialize the Index Array if (FALSE == m_FileIdx.fInit()) return ResultFromScode( E_FAIL ); // Cache essentail column metadata if (FAILED(GatherColumnInfo())) return ResultFromScode( E_FAIL ); // Obtain the starting offset for each row seekg( m_ulDataTypeOffset ); ulSavePos = tellg(); getline( m_pvInput, MAX_INPUT_BUFFER ); while (good() && !eof()) { //Ignore Deleted Lines if ('@' != *m_pvInput && '\0' != *m_pvInput) m_FileIdx.SetIndex( ulDex++, ulSavePos ); ulSavePos = tellg(); getline( m_pvInput, MAX_INPUT_BUFFER ); } // Store the number of rows m_cRows = ulDex - 1; clear(); return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Check if the row has already been deleted // // @rdesc HRESULT // @flag S_OK | Row already deleted // @flag S_FALSE | Row not deleted // HRESULT CFileIO::IsDeleted ( DBCOUNTITEM ulRow //@parm IN | Row to Check ) { // Already deleted if (TRUE == m_FileIdx.IsDeleted( ulRow )) return ResultFromScode( S_OK ); else return ResultFromScode( S_FALSE ); } //-------------------------------------------------------------------- // @mfunc Fill the row with '@' characters, the deletion pattern.. // And set the Deletion status flag in the index class. // // @rdesc HRESULT // @flag S_OK | Deleted Row // @flag E_FAIL | Row Number was invalid or problem deleting. // HRESULT CFileIO::DeleteRow ( DBCOUNTITEM ulRow //@parm IN | Row to Delete ) { assert( is_open()); assert( m_pvInput ); // Check the Row Number if ((ulRow < 1) || (ulRow > m_cRows)) return ResultFromScode( E_FAIL ); // If already deleted, just ignore. if (TRUE == m_FileIdx.IsDeleted( ulRow )) return ResultFromScode( S_OK ); // Set the File Pointer seekg( m_FileIdx.GetRowOffset( ulRow )); clear(); // Delete the row in the file and mark the status // as deleted in the index Array getline( m_pvInput, MAX_INPUT_BUFFER ); if (good()) { // Set the number bytes in the stream minus // the null terminator to this pattern memset( m_pvInput, '@', gcount() - 1 ); seekp( m_FileIdx.GetRowOffset( ulRow )); clear(); write( m_pvInput, gcount() - 1 ); if (bad()) return ResultFromScode( E_FAIL ); else flush(); } else return ResultFromScode( E_FAIL ); m_FileIdx.DeleteRow( ulRow ); return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Establish the Binding Information for the given file // // @rdesc HRESULT // @flag S_OK | Binding set // @flag E_FAIL | Problem setting the binding // HRESULT CFileIO::SetColumnBind ( DBORDINAL cCols, //@parm IN | Column Number PCOLUMNDATA pColumn //@parm IN | Pointer to the Data Area ) { assert( is_open()); assert( m_rgpColumnData ); // If the column number is in range then return // the pointer if ((0 == cCols) || (m_cColumns < cCols)) return ResultFromScode( E_FAIL ); // Expect valid pointer assert( pColumn ); m_rgpColumnData[cCols] = pColumn; return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Fetch the row data from the stream to the internal data // buffers // // @rdesc HRESULT // @flag S_OK | Row Retrieve successfully // @flag S_FALSE | End of Result Set // @flag E_FAIL | Row could not be retrieved // HRESULT CFileIO::Fetch ( DBCOUNTITEM ulRow //@parm IN | Row to retrieve ) { assert( is_open()); assert( m_rgpColumnData ); assert( m_rgsdwMaxLen ); // Check the Row Number if ((ulRow < 1)) return ResultFromScode( E_FAIL ); //Check end of Result Set if (ulRow > m_cRows) return ResultFromScode( S_FALSE ); // Set the File Pointer to the row. seekg( m_FileIdx.GetRowOffset( ulRow )); clear(); // If already deleted, just ignore. if (TRUE == m_FileIdx.IsDeleted( ulRow )) return ResultFromScode( S_OK ); // Retrieve the column names record getline( m_pvInput, MAX_INPUT_BUFFER ); if (good() && 0 < gcount()) { //Flag a Delete from another user if ( strncmp(m_pvInput, "@", sizeof(char)) == 0 ) { DeleteRow( ulRow ); return ResultFromScode( S_OK ); } // Parse the row return ParseRowValues(); } return ResultFromScode( E_FAIL ); } //-------------------------------------------------------------------- // @mfunc Tokenize the Data values and put them into the correct // binding areas // // @rdesc HRESULT // @flag S_OK | Parsing yielded no Error // @flag E_FAIL | Data value could not be parsed or stored // HRESULT CFileIO::ParseRowValues ( void ) { DBORDINAL cColumns = 0; DWORD cQuotes = 0; LPTSTR pvCopy, pvInput, pLastQuote; pLastQuote = NULL; pvCopy = NULL; pvInput = m_pvInput; assert( pvInput ); assert( m_cColumns > 0 ); while ('\0' != *pvInput) { // Check for Quotes if (0 == strncmp( "\"", pvInput, sizeof( char ))) { pLastQuote = pvInput; cQuotes++; goto TermCheck; } // Check for Comma // NOTE: THIS won't handle """ if (0 == strncmp( ",", pvInput, sizeof( char )) && 0 == cQuotes % 2) { if (pLastQuote) memcpy( pLastQuote, "", sizeof( char )); else memcpy( pvInput, "", sizeof( char )); // Increment Columns processed cColumns++; // TRACE(pvCopy ? pvCopy : ""); if (FAILED( FillBinding( cColumns, pvCopy ))) return ResultFromScode( E_FAIL ); pLastQuote = NULL; pvCopy = NULL; cQuotes = 0; goto TermCheck; } //Valid First character for next column if (NULL == pvCopy) pvCopy = pvInput; TermCheck: // Check for Final Null Terminator if (0 == strncmp( "", (pvInput+1), sizeof(char))) { //If we are to the null terminator and have unbalanced "'s //then we fail if (0 != cQuotes % 2) return ResultFromScode( E_FAIL ); if (pLastQuote) memcpy( pLastQuote, "", sizeof(char)); // Increment Columns processed cColumns++; // TRACE(pvCopy ? pvCopy : ""); if (FAILED( FillBinding( cColumns, pvCopy ))) return ResultFromScode( E_FAIL ); } pvInput++; } // Check that we returned the correct number of columns if (cColumns < m_cColumns) return ResultFromScode( E_FAIL ); return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Based on the given bindings and column data, put the data // in the correct area, update the status and length fields // // @rdesc HRESULT // @flag S_OK | Data copied to the specified location // HRESULT CFileIO::FillBinding ( DBORDINAL cColumn, //@parm IN | Column that value is for LPTSTR pvCopy //@parm IN | Pointer to data value to transfer ) { assert( m_rgswColType ); assert( m_rgpColumnData ); assert( m_rgsdwMaxLen ); // Null Value if (!pvCopy) { m_rgpColumnData[cColumn]->dwStatus = DBSTATUS_S_ISNULL; return ResultFromScode( S_OK ); } switch (m_rgswColType[cColumn]) { case TYPE_CHAR: lstrcpyn((LPTSTR) m_rgpColumnData[cColumn]->bData, pvCopy, m_rgsdwMaxLen[cColumn] + sizeof( char ) ); m_rgpColumnData[cColumn]->uLength = lstrlen( (LPTSTR) m_rgpColumnData[cColumn]->bData ); m_rgpColumnData[cColumn]->dwStatus = DBSTATUS_S_OK; break; case TYPE_SLONG: *(ULONG*) m_rgpColumnData[cColumn]->bData = atol( pvCopy ); m_rgpColumnData[cColumn]->uLength = 4; m_rgpColumnData[cColumn]->dwStatus = DBSTATUS_S_OK; break; default: assert( !"Unknown Data Type" ); break; } return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Given a pointer to the new data and the row the the data is // for, write the data to the file. // // @rdesc HRESULT // @flag S_OK | Record and Indexes updated // @flag E_FAIL | Problems updating record // HRESULT CFileIO::UpdateRow ( DBCOUNTITEM ulRow, //@parm IN | Row to update BYTE* pbProvRow, //@parm IN | Data to update row with. UPDTYPE eUpdateType //@parm IN | What type of update ) { LPSTR pvInput = m_pvInput; CHAR szTmpBuff[MAX_BIND_LEN+1]; PCOLUMNDATA pColData; DBORDINAL cCols; size_t nCnt; assert( is_open()); assert( m_rgdwDataOffsets ); // Fix up Row count if( eUpdateType == INSERT ) m_cRows++; // Delete old Row if( (eUpdateType == UPDATE) && (FAILED( DeleteRow( ulRow ))) ) return ResultFromScode( E_FAIL ); // Fix up Row offset value seekg( 0, ios::end ); m_FileIdx.SetIndex( ulRow, tellg() ); // Check the Row Number if ((ulRow < 0) || (ulRow > m_cRows)) return ResultFromScode( E_FAIL ); // Updated Rows are added to the end of the file, the row number will // remain the same until the rowset is closed, because the old // offset is deleted and the new is put in it's place. for (cCols = 1; cCols <= m_cColumns; cCols++) { pColData = GetColumnData(cCols, (ROWBUFF *)pbProvRow); // Handle NULL Data if (pColData->dwStatus != DBSTATUS_S_ISNULL) { switch (m_rgswColType[cCols]) { case TYPE_CHAR: StringCchCopyNA( szTmpBuff, sizeof(szTmpBuff), (LPSTR)pColData->bData, pColData->uLength ); StringCchPrintfExA( pvInput, MAX_INPUT_BUFFER, &pvInput, &nCnt, 0, "\"%s\"", szTmpBuff ); break; case TYPE_SLONG: StringCchPrintfExA( pvInput, MAX_INPUT_BUFFER, &pvInput, &nCnt, 0, "%d", (signed long) *pColData->bData ); break; default: assert( !"Unknown Data Type" ); break; } } //Calculate the next append area //pvInput = (pvInput + (nCnt * sizeof(char))); if (cCols == m_cColumns) StringCchCopyA( pvInput, nCnt, "\n" ); else { StringCchCopyA( pvInput, nCnt, "," ); pvInput += 1; } } // Write Stream to File // If Update change in place or InsertRow add to the end seekg( m_FileIdx.GetRowOffset( ulRow ) ); clear(); write( m_pvInput, lstrlen( m_pvInput )); if (bad()) return ResultFromScode( E_FAIL ); else flush(); return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Returns ::GetColumnsInfo data // // @rdesc HRESULT // @flag S_OK | All output populated correctly // @flag E_FAIL | Problems gathering Column Information // HRESULT CFileIO::GatherColumnInfo() { HRESULT hr; LPWSTR lpwstr; int cchFree, cchWide; DBCOLUMNINFO * pcolinfo = NULL; DBLENGTH dwOffset; DBORDINAL cCols; int cbName; SWORD swCSVType; UDWORD cbColDef; DWORD dwdbtype; BOOL fSigned; SDWORD * rgcbLen = NULL; DBLENGTH * rgdwOffsets = NULL; // Heap for column names. // Commit it all, then de-commit and release once we know size. m_pbHeap = (BYTE *) VirtualAlloc( NULL, MAX_HEAP_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE ); if( !m_pbHeap ) { hr = ResultFromScode(E_OUTOFMEMORY); goto EXIT; } //---------------------------------- // Gather column info //---------------------------------- lpwstr = (LPWSTR) m_pbHeap; cchFree = MAX_HEAP_SIZE / 2; pcolinfo = &(m_rgdbcolinfo[1]); rgcbLen = m_rgsdwMaxLen; rgdwOffsets = m_rgdwDataOffsets; dwOffset = offsetof( ROWBUFF, cdData ); for (cCols=1; cCols <= m_cColumns; cCols++, pcolinfo++) { LPTSTR ptstrName; // Get Column Names and Lengths hr = GetColumnName(cCols, &ptstrName); if (FAILED( hr )) { hr = ResultFromScode( E_FAIL ); goto EXIT; } // Store the Column Name in the Heap if (cchFree) { cbName = lstrlen( ptstrName ); if (cbName) cchWide = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, ptstrName, (int) cbName, lpwstr, cchFree ); else cchWide = 0; pcolinfo->pwszName = lpwstr; lpwstr += cchWide; cchFree -= cchWide; if (cchFree) { *lpwstr++ = 0; cchFree--; } else *(lpwstr - 1) = 0; } else pcolinfo->pwszName = NULL; // Get DataTypes and Precision hr = GetDataTypes(cCols, &swCSVType, // CSV data type &cbColDef, // Precision &fSigned // Signed or Unsigned ); if (FAILED( hr )) return ResultFromScode( E_FAIL ); // We use ordinal numbers for our columns pcolinfo->columnid.eKind = DBKIND_GUID_PROPID; pcolinfo->columnid.uGuid.guid = GUID_NULL; pcolinfo->columnid.uName.ulPropid = (ULONG) cCols; // Determine the OLE DB type, for binding. hr = GetInternalTypeFromCSVType( swCSVType, // in fSigned, // in &dwdbtype ); // out, DBTYPE to show client if (FAILED( hr )) { hr = ResultFromScode( E_FAIL ); goto EXIT; } // Check for overflow of size. rgcbLen[cCols] = cbColDef; rgcbLen[cCols] = MIN( rgcbLen[cCols], MAX_BIND_LEN ); pcolinfo->iOrdinal = cCols; pcolinfo->wType = (DBTYPE) dwdbtype; pcolinfo->pTypeInfo = NULL; pcolinfo->ulColumnSize = rgcbLen[cCols]; pcolinfo->bPrecision = (BYTE) ~0; pcolinfo->bScale = (BYTE) ~0; pcolinfo->dwFlags = 0; // Is it a string datatype if(pcolinfo->wType != DBTYPE_STR) pcolinfo->bPrecision = (BYTE)cbColDef; // Is it a fixed length datatype if(pcolinfo->wType != DBTYPE_STR) pcolinfo->dwFlags |= DBCOLUMNFLAGS_ISFIXEDLENGTH; // We do support nulls pcolinfo->dwFlags |= DBCOLUMNFLAGS_ISNULLABLE; pcolinfo->dwFlags |= DBCOLUMNFLAGS_MAYBENULL; //We should always be able to write to the column pcolinfo->dwFlags |= DBCOLUMNFLAGS_WRITE; // Set the offset from the start of the row, // for this column, then advance past. dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL ); rgdwOffsets[cCols] = dwOffset; dwOffset += offsetof( COLUMNDATA, bData ) + rgcbLen[cCols]; } m_cbRowSize = ROUND_UP( dwOffset, COLUMN_ALIGNVAL ); m_cbHeapUsed = MAX_HEAP_SIZE - 2*cchFree; // Decommit unused memory in our column-name buffer. // We know it will never grow beyond what it is now. // Decommit all pages past where we currently are. BYTE *pDiscardPage; ptrdiff_t ulSize; pDiscardPage = (BYTE *) ROUND_UP( lpwstr, g_dwPageSize ); ulSize = MAX_HEAP_SIZE - (pDiscardPage - m_pbHeap); if (ulSize > 0) VirtualFree( pDiscardPage, ulSize, MEM_DECOMMIT ); assert( '\0' == (*lpwstr = '\0')); // We shouldn't generate a mem fault. EXIT: if( FAILED(hr) ) { if( m_pbHeap ) { VirtualFree((VOID *) m_pbHeap, 0, MEM_RELEASE ); } m_cbRowSize = m_cbHeapUsed = 0; return hr; } else return ResultFromScode( S_OK ); } //-------------------------------------------------------------------- // @mfunc Given a ROWBUFF and a column ordinal, this function // returns a ptr to a particular column's COLUMNDATA // // COLUMNDATA * CFileIO::GetColumnData ( DBORDINAL cCols, ROWBUFF * pRowBuff ) { assert( is_open()); assert( m_rgdwDataOffsets ); // If the column number is in range then return // the pointer if ((0 == cCols) || (m_cColumns < cCols)) return NULL; return (COLUMNDATA *)((BYTE *)pRowBuff + m_rgdwDataOffsets[cCols]); }