//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1994 - 2000.
//
//  File:       colinfo.cxx
//
//  Contents:   Column information for rowsets
//
//  Classes:    CColumnsInfo
//
//  Notes:      Designed as an aggregated class of an IRowset or an
//              IQuery implementation.
//
//  History:    04 Feb 1995     AlanW   Created
//
//--------------------------------------------------------------------------

#include "pch.cxx"
#pragma hdrstop

#include <colinfo.hxx>
#include <query.hxx>
#include <tblvarnt.hxx>

#include "tblrowal.hxx"
#include "tabledbg.hxx"

// Always bind as DBTYPE_VARIANT, so we can use provider-owned memory

#define ALWAYS_USE_VARIANT_BINDING

ULONG CColumnsInfo::_nUnique = 0;

const static GUID guidBmk = DBBMKGUID;

//+-------------------------------------------------------------------------
//
//  Method:     CColumnsInfo::QueryInterface, public
//
//  Synopsis:   Invokes QueryInterface on controlling unknown object
//
//  Arguments:  [riid]        -- interface ID
//              [ppvObject]   -- returned interface pointer
//
//  Returns:    SCODE
//
//  History:    04 Feb 1995     AlanW   Created
//
//--------------------------------------------------------------------------

STDMETHODIMP CColumnsInfo::QueryInterface(
    REFIID riid,
    void **ppvObject)
{
    return _rUnknown.QueryInterface(riid,ppvObject);
} //QueryInterface


//+-------------------------------------------------------------------------
//
//  Method:     CColumnsInfo::AddRef, public
//
//  Synopsis:   Invokes AddRef on controlling unknown object
//
//  Returns:    ULONG
//
//  History:    04 Feb 1995     AlanW   Created
//
//--------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CColumnsInfo::AddRef()
{
    return _rUnknown.AddRef();
} //AddRef


//+-------------------------------------------------------------------------
//
//  Method:     CColumnsInfo::Release, public
//
//  Synopsis:   Invokes Release on controlling unknown object
//
//  Returns:    ULONG
//
//  History:    04 Feb 1995     AlanW   Created
//
//--------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CColumnsInfo::Release()
{
    return _rUnknown.Release();
} //Release


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::CColumnsInfo, public
//
//  Synopsis:   Creates a column information class
//
//  Arguments:  [cols]        -- a reference to the output column set, pidmapped
//              [pidmap]      -- property IDs and names for the columns
//              [ErrorObject] -- a reference to enclosing object's error obj.
//              [rUnknown]    -- a reference to the controlling IUnknown
//              [fSequential] -- TRUE if the query is sequential
//
//  Notes:
//
//  History:    04 Feb 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CColumnsInfo::CColumnsInfo(
        CColumnSet const & cols,
        CPidMapperWithNames const & pidmap,
        CCIOleDBError & ErrorObject,
        IUnknown &  rUnknown,
        BOOL        fSequential ) :
        _idUnique(0),
        _rUnknown(rUnknown),
        _fSequential(fSequential),
        _fChaptered(FALSE),
        _cbRowWidth(0),
        _cColumns( cols.Count() ),
        _cBoundColumns(0),
        _iColRowId(pidInvalid),
        _pColumns(0),
        _pidmap( cols.Count()+1 ),
        _ErrorObject( ErrorObject ),
        _fNotPrepared(FALSE)
{
    _SetColumns(cols, pidmap, fSequential);
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::CColumnsInfo, public
//
//  Synopsis:   Creates an empty column information class
//
//  Arguments:  [rUnknown]    -- a reference to the controlling IUnknown
//              [ErrorObject] -- a reference to enclosing object's error obj.
//
//  Notes:      Used in the command object where column information may
//              change, depending upon the command.
//
//  History:    11 Aug 1997     AlanW   Created
//
//----------------------------------------------------------------------------

CColumnsInfo::CColumnsInfo(
        IUnknown &  rUnknown,
        CCIOleDBError & ErrorObject,
        BOOL fNotPrepared) :
        _idUnique(_GetNewId()),
        _rUnknown(rUnknown),
        _fSequential(FALSE),
        _fChaptered(FALSE),
        _cbRowWidth(0),
        _cColumns( 0 ),
        _cBoundColumns(0),
        _iColRowId(pidInvalid),
        _pColumns(0),
        _pidmap( 0 ),
        _ErrorObject( ErrorObject ),
        _fNotPrepared(fNotPrepared)
{

}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::~CColumnsInfo, public
//
//  Synopsis:   Destroys a column information class
//
//  Notes:
//
//  History:    12 Feb 1995     AlanW   Created
//
//----------------------------------------------------------------------------

CColumnsInfo::~CColumnsInfo( )
{
    delete _pColumns;
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::InitColumns, public
//
//  Synopsis:   Initializes or reinitializes columns.
//
//  Arguments:  [cols]        -- a reference to the output column set, pidmapped
//              [pidmap]      -- property IDs and names for the columns
//              [fSequential] -- TRUE if the query is sequential
//
//  Notes:
//
//  History:    11 Aug 1997     AlanW   Created
//
//----------------------------------------------------------------------------

void CColumnsInfo::InitColumns (
        CColumnSet const & cols,
        CPidMapperWithNames const & pidmap,
        BOOL        fSequential )
{
    _pidmap.Clear();
    _cColumns = cols.Count();
    _fSequential = fSequential;
    _SetColumns(cols, pidmap, fSequential);
    _fChaptered = FALSE;
    _fNotPrepared = FALSE;
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::InitColumns, public
//
//  Synopsis:   Reinitializes columns to be null.
//
//  Arguments:  [fNotPrepared]  - TRUE if GetColumnInfo and MapColumnIDs should
//                                return DB_E_NOTPREPARED for a command object
//
//  Notes:
//
//  History:    11 Aug 1997     AlanW   Created
//
//----------------------------------------------------------------------------

void CColumnsInfo::InitColumns ( BOOL fNotPrepared )
{
    _pidmap.Clear();
    _cColumns = 0;
    _fSequential = FALSE;
    _fChaptered = FALSE;
    _fNotPrepared = fNotPrepared;
}

//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::_SetColumns, private
//
//  Synopsis:   Initializes or reinitializes columns.
//
//  Arguments:  [cols]        -- a reference to the output column set, pidmapped
//              [pidmap]      -- property IDs and names for the columns
//              [fSequential] -- TRUE if the query is sequential
//
//  Notes:
//
//  History:    11 Aug 1997     AlanW   Created
//
//----------------------------------------------------------------------------

void CColumnsInfo::_SetColumns (
        CColumnSet const & cols,
        CPidMapperWithNames const & pidmap,
        BOOL        fSequential )
{
    Win4Assert( 0 == _pColumns && 0 == _cbRowWidth );
    _idUnique = _GetNewId();

    //
    //  We want the PidMapper to give back 1-based column numbers;
    //  add either a null propspec or the bookmark column as its first element.
    //

    if (fSequential)
    {
        CFullPropSpec nullCol;
        _pidmap.NameToPid( nullCol );
    }
    else
    {
        CFullPropSpec bmkCol( guidBmk, PROPID_DBBMK_BOOKMARK );
        _pidmap.NameToPid( bmkCol );
    }

    for (unsigned i = 0; i < _cColumns; i++)
    {
        PROPID pidTmp = cols.Get(i);
        const CFullPropSpec & ColId = *pidmap.Get(pidTmp);

        if (ColId.IsPropertyPropid() &&
            ColId.GetPropSet() == guidBmk)
        {
            Win4Assert( !fSequential );
            if (ColId.GetPropertyPropid() == PROPID_DBBMK_BOOKMARK)
            {
                if (0 != pidmap.GetFriendlyName(pidTmp))
                {
                    _pidmap.SetFriendlyName( 0, pidmap.GetFriendlyName(pidTmp) );
                }
                continue;
            }
            else if (ColId.GetPropertyPropid() == PROPID_DBBMK_CHAPTER)
            {
                _fChaptered = TRUE;
            }
        }
        PROPID pidNew = _pidmap.NameToPid( ColId );
        if (0 != pidmap.GetFriendlyName(pidTmp))
        {
            _pidmap.SetFriendlyName( pidNew, pidmap.GetFriendlyName(pidTmp) );
        }
    }

    //  In case of duplicate columns, _cColumns needs to be adjusted.
    Win4Assert( _pidmap.Count() > 1 );
    _cColumns = _pidmap.Count() - 1;
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::MapColumnID, private
//
//  Synopsis:   Map a column identifier to its column number in the
//              cursor.
//
//  Arguments:  [pColumnId] - A pointer to the column identifier.
//
//  Returns:    The column number (1-based).  Returns DB_INVALIDCOLUMN
//              on error.
//
//  History:    04 Feb 1995     AlanW   Created
//
//----------------------------------------------------------------------------

ULONG CColumnsInfo::MapColumnID(
    const DBID *      pColumnId
) {
    PROPID pid = pidInvalid;

    if (pColumnId->eKind == DBKIND_PGUID_PROPID ||
        pColumnId->eKind == DBKIND_PGUID_NAME)
    {
        DBID dbcolMapped = *pColumnId;
        dbcolMapped.uGuid.guid = *pColumnId->uGuid.pguid;

        if (pColumnId->eKind == DBKIND_PGUID_PROPID)
            dbcolMapped.eKind = DBKIND_GUID_PROPID;
        else
            dbcolMapped.eKind = DBKIND_GUID_NAME;

        pid = _pidmap.NameToPid(dbcolMapped);
    }
    else
    {
        pid = _pidmap.NameToPid(*pColumnId);
    }

    tbDebugOut(( DEB_ITRACE, "pid: 0x%x, _cColumns: %d\n",
                 pid, _cColumns ));

    if (pid == pidInvalid || pid > _cColumns || (pid == 0 && _fSequential) )
        return (ULONG) DB_INVALIDCOLUMN;

    return pid;
}

//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::MapColumnIDs, public
//
//  Synopsis:   Map a column identifier to its column number in the
//              rowset.
//
//  Arguments:  [cColumnIDs] -- # of elements in the arrays
//              [rgColumnIDs]  -- A pointer to the column identifiers
//              [rgColumns] -- an array in which to return the column numbers.
//
//  Returns:    SCODE - DB_S_ERRORSOCCURRED, an element of rgColumnIDs was
//                      invalid
//
//  Notes:      Column numbers are 1-based.
//
//----------------------------------------------------------------------------

STDMETHODIMP CColumnsInfo::MapColumnIDs(
    DBORDINAL           cColumnIDs,
    const DBID          rgColumnIDs[],
    DBORDINAL           rgColumns[])
{
    _ErrorObject.ClearErrorInfo();

    SCODE sc = S_OK;

    if ((0 != cColumnIDs && 0 == rgColumnIDs) ||
         0 == rgColumns)
        return _ErrorObject.PostHResult(E_INVALIDARG, IID_IColumnsInfo);

    if ( 0 == cColumnIDs )
        return S_OK;

    if ( _fNotPrepared )
        return _ErrorObject.PostHResult(DB_E_NOTPREPARED, IID_IColumnsInfo);

    if ( 0 == _cColumns )
        return _ErrorObject.PostHResult(DB_E_NOCOMMAND, IID_IColumnsInfo);

    unsigned cBadMapping = 0;
    TRY
    {
        for (ULONG i = 0; i < cColumnIDs; i++)
        {
            ULONG ulColID = MapColumnID( &rgColumnIDs[i] );
            rgColumns[i] = ulColID;
            if (ulColID == DB_INVALIDCOLUMN)
                cBadMapping++;
        }
    }
    CATCH( CException, e )
    {
        _ErrorObject.PostHResult(e.GetErrorCode(), IID_IColumnsInfo);
        sc = GetOleError(e);
    }
    END_CATCH;

    if (SUCCEEDED(sc) && cBadMapping)
        sc = (cBadMapping == cColumnIDs) ? DB_E_ERRORSOCCURRED :
                                           DB_S_ERRORSOCCURRED;

    return sc;
} //MapColumnIDs


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::GetColumnInfo, public
//
//  Synopsis:   Return information about the columns in the rowset.
//
//  Arguments:  [pcColumns] - A pointer to where the number of columns
//                            will be returned.
//              [prgInfo] -   A pointer to where a pointer to an array of
//                            DBCOLUMNINFO structures describing the columns
//                            will be returned.  This must be freed by the
//                            caller.
//              [ppStringsBuffer] - A pointer to where extra data for strings
//                            will be returned.  This must be freed by the
//                            caller if non-null.
//
//  Returns:    SCODE
//
//  Notes:      Some columns are standard columns available for all file
//              stores.  For these columns, full information about data
//              type and sizes can be returned.  For any other columns,
//              we can only say that it has a variant type.
//
//  History:    07 Nov 1994     AlanW   Created
//              04 Feb 1995     AlanW   Moved to CColumnsInfo and rewritten
//
//----------------------------------------------------------------------------

STDMETHODIMP CColumnsInfo::GetColumnInfo(
    DBORDINAL *             pcColumns,
    DBCOLUMNINFO * *    prgInfo,
    WCHAR * *           ppStringsBuffer)
{
    _ErrorObject.ClearErrorInfo();

    SCODE scResult = S_OK;

    //
    // Initialize arguments before returning errors
    //
    if ( pcColumns)
        *pcColumns = 0;
    if ( prgInfo )
        *prgInfo = 0;
    if (ppStringsBuffer )
        *ppStringsBuffer = 0;

    if (0 == pcColumns ||
        0 == prgInfo ||
        0 == ppStringsBuffer)
        return _ErrorObject.PostHResult(E_INVALIDARG, IID_IColumnsInfo);

    if ( _fNotPrepared )
        return _ErrorObject.PostHResult(DB_E_NOTPREPARED, IID_IColumnsInfo);

    if ( 0 == _cColumns )
        return _ErrorObject.PostHResult(DB_E_NOCOMMAND, IID_IColumnsInfo);

    TRY
    {
        unsigned iFirstCol = _fSequential ? 1 : 0;
        unsigned cColumns = GetColumnCount() + 1 - iFirstCol;

        //
        //  The total size required for the output array depends upon
        //  the size of variable data discovered in the column information.
        //  Although we could reallocate the memory we'll be writing into,
        //  we'll just run through the loop twice, once to compute the
        //  needed space, and the second time to copy the data out after
        //  doing our allocation.
        //
        ULONG cchNames = 0;

        for (unsigned iCol = iFirstCol; iCol <= GetColumnCount(); iCol++)
        {
            const CFullPropSpec & ColId = *_pidmap.Get(iCol);

            if (ColId.IsPropertyName())
            {
                cchNames += wcslen(ColId.GetPropertyName()) + 1;
            }

            WCHAR const * pwszColName = _pidmap.GetFriendlyName(iCol);
            if (0 == pwszColName)
                pwszColName = _FindColumnInfo(ColId).pwszName;

            if (pwszColName)
            {
                cchNames += wcslen(pwszColName) + 1;
            }
        }

        XArrayOLE<DBCOLUMNINFO> ColumnInfo( cColumns );
        XArrayOLE<WCHAR> StringBuf( cchNames );

        DBCOLUMNINFO *pColInfo = ColumnInfo.GetPointer();
        WCHAR * pwcNames = StringBuf.GetPointer();

        for (iCol = iFirstCol; iCol <= GetColumnCount(); iCol++, pColInfo++)
        {
            const CFullPropSpec & ColId = *_pidmap.Get(iCol);
            const DBCOLUMNINFO & rColumnInfo = _FindColumnInfo( ColId );

            //
            //  Copy the prototype column information, then update
            //  specific fields in the column info:
            //      column number
            //      column ID
            //      copies of strings
            //
            *pColInfo = rColumnInfo;

            pColInfo->iOrdinal = iCol;

            pColInfo->columnid.uGuid.guid = ColId.GetPropSet();
            if (ColId.IsPropertyName())
            {
                pColInfo->columnid.eKind = DBKIND_GUID_NAME;
                ULONG cch = wcslen(ColId.GetPropertyName()) + 1;
                RtlCopyMemory(pwcNames, ColId.GetPropertyName(),
                                cch * sizeof (WCHAR));
                pColInfo->columnid.uName.pwszName = pwcNames;
                pwcNames += cch;
            }
            else
            {
                Win4Assert(ColId.IsPropertyPropid());
                pColInfo->columnid.eKind = DBKIND_GUID_PROPID;
                pColInfo->columnid.uName.ulPropid = ColId.GetPropertyPropid();
            }

            WCHAR const * pwszColName = _pidmap.GetFriendlyName(iCol);
            if (0 == pwszColName)
                pwszColName = _FindColumnInfo(ColId).pwszName;

            if (pwszColName)
            {
                ULONG cch = wcslen(pwszColName) + 1;
                RtlCopyMemory(pwcNames, pwszColName, cch * sizeof (WCHAR));
                pColInfo->pwszName = pwcNames;
                pwcNames += cch;
            }
        }

        Win4Assert( (unsigned)(pColInfo - ColumnInfo.GetPointer()) == cColumns );

        *prgInfo = ColumnInfo.Acquire();
        if (cchNames > 0)
            *ppStringsBuffer = StringBuf.Acquire();

        *pcColumns = cColumns;
    }
    CATCH( CException, e )
    {
        scResult = e.GetErrorCode();
        _ErrorObject.PostHResult(scResult, IID_IColumnsInfo);
        if (scResult != E_OUTOFMEMORY)
            scResult = E_FAIL;
    }
    END_CATCH;

    return scResult;
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::SetColumnBindings, public
//
//  Synopsis:   Set current column bindings on the cursor.  Save in
//              member variables.  Workid is always added to the
//              bindings for movable rowsets for use with bookmarks
//              and hRows.  Space for a USHORT reserved for row buffer
//              refcounting is always allocated.
//
//  Arguments:  [rpQuery] - a reference to the PQuery for the query
//              [hCursor] - a reference to the hCursor to have column
//                              bindings set on.
//              [obRowRefcount] - on return, offset into the row buffer
//                              where a USHORT reference count can be stored.
//              [obRowId] - on return, offset into the row buffer where
//                              the row identifier is stored.  Not valid for
//                              sequential rowsets.
//
//  Returns:    Nothing.  Throws on errors.
//
//  Notes:      Initializes the private members _cbRowWidth and _pColumns.
//
//  History:    04 Feb 1995     AlanW   Created
//
//----------------------------------------------------------------------------

void    CColumnsInfo::SetColumnBindings(
    PQuery &    rpQuery,
    ULONG       hCursor,
    ULONG       &obRowRefcount,
    ULONG       &obRowId,
    ULONG       &obChaptRefcount,
    ULONG       &obChaptId
) {
    CTableRowAlloc RowMap( 0 );
    USHORT maxAlignment = sizeof (USHORT);

    obRowRefcount = RowMap.AllocOffset( sizeof (USHORT),
                                        sizeof (USHORT),
                                        TRUE );

    if (_fChaptered)
        obChaptRefcount = RowMap.AllocOffset( sizeof (USHORT),
                                              sizeof (USHORT),
                                              TRUE );
    else
        obChaptRefcount = 0xFFFFFFFF;

    obRowId = 0xFFFFFFFF;
    obChaptId = 0xFFFFFFFF;
    BOOL fAddedWorkId = FALSE;
    BOOL fMayDefer = FALSE;

    // +1 In case WorkID or Path is added for rowid

    XPtr<CTableColumnSet> XColumns( new CTableColumnSet( GetColumnCount() + 1 ));

    unsigned cBoundColumns = 0;

    tbDebugOut(( DEB_ITRACE, "original column count: %d\n", GetColumnCount() ));
    for (unsigned iCol = 1; iCol <= GetColumnCount(); iCol++)
    {
        const CFullPropSpec & ColId = *_pidmap.Get( iCol );
        const DBCOLUMNINFO & rColumnInfo = _FindColumnInfo( ColId );

        tbDebugOut(( DEB_ITRACE, "colinfo::set, top of loop, cBoundColumns: %d\n",
                     cBoundColumns ));
        tbDebugOut(( DEB_ITRACE, "adding '%ws'\n", rColumnInfo.pwszName ));

        //
        //  If this is bookmark column, it will be mapped to the row ID
        //  column.  It's only valid for locatable rowsets.
        //
        if ( (rColumnInfo.dwFlags & DBCOLUMNFLAGS_ISBOOKMARK) &&
             ColId.IsPropertyPropid() &&
             ColId.GetPropertyPropid() == PROPID_DBBMK_BOOKMARK)
        {
            tbDebugOut(( DEB_ITRACE, "skipping bookmark column\n" ));

            Win4Assert(! _fSequential );
            if (_fSequential)
                THROW(CException(E_FAIL));
            continue;
        }

        // the self columns is resolved in the accessor -- no binding needed

        if ( ( ColId.IsPropertyPropid() ) &&
             ( ColId.GetPropertyPropid() == PROPID_DBSELF_SELF ) &&
             ( ColId.GetPropSet() == DBCOL_SELFCOLUMNS ) )
        {
            continue;
        }

        //
        //  Create the new column.  Note that its PropID is the
        //  1-based column ID.
        //
        XPtr<CTableColumn> TableCol(new CTableColumn( iCol ));

#ifndef ALWAYS_USE_VARIANT_BINDING
        VARTYPE vt = rColumnInfo.wType;

        switch (vt)
        {
        case DBTYPE_VARIANT:
        {
#endif // ndef ALWAYS_USE_VARIANT_BINDING

            fMayDefer = TRUE;

            TableCol->SetValueField( DBTYPE_VARIANT,
                                     RowMap.AllocOffset( sizeof (PROPVARIANT),
                                                         sizeof (LONGLONG),
                                                         TRUE ),
                                     sizeof (PROPVARIANT));

            // The status column is interesting for all columns

            TableCol->SetStatusField( RowMap.AllocOffset( sizeof (BYTE),
                                                          sizeof (BYTE),
                                                          TRUE ),
                                      sizeof (BYTE));

            // Length is interesting, especially when the value is deferred

            TableCol->SetLengthField( RowMap.AllocOffset( sizeof (ULONG),
                                                          sizeof (ULONG),
                                                          TRUE ),
                                      sizeof (ULONG));

            USHORT cbData, cbAlignment, rgfFlags;
            CTableVariant::VartypeInfo(DBTYPE_VARIANT, cbData, cbAlignment, rgfFlags);

            if ( cbAlignment > maxAlignment)
                maxAlignment = cbAlignment;

#ifndef ALWAYS_USE_VARIANT_BINDING
            break;
        }

        case DBTYPE_DATE:
        case DBTYPE_WSTR:
        case DBTYPE_STR:
            //
            //  Adjust DBTYPEs from the column info into ones that are
            //  better for binding.
            //
            if (vt == DBTYPE_DATE)
                vt = VT_FILETIME;
            else if (vt == DBTYPE_WSTR)
                vt = VT_LPWSTR;
            else if (vt == DBTYPE_STR)
                vt = VT_LPSTR;

            // NOTE: fall through

        default:

            USHORT cbData, cbAlignment, rgfFlags;
            CTableVariant::VartypeInfo(vt, cbData, cbAlignment, rgfFlags);

            if (rgfFlags & CTableVariant::MultiSize)
                cbData = (USHORT) rColumnInfo.ulColumnSize;

            Win4Assert(cbData != 0 || vt == VT_EMPTY);

            if (cbData == 0 && vt != VT_EMPTY)
            {
                tbDebugOut(( DEB_WARN,
                        "CColumnInfo::SetColumnBindings - Unknown variant type %4x\n",
                        vt));
            }

            if (cbAlignment)
            {
                if (cbAlignment > maxAlignment)
                {
                    maxAlignment = cbAlignment;
                }
            }
            else
            {
                cbAlignment = 1;
            }

            if (cbData != 0)
            {
                TableCol->SetValueField( vt,
                                         RowMap.AllocOffset( cbData,
                                                             cbAlignment,
                                                             TRUE ),
                                         cbData);

                Win4Assert( 0 == ( (TableCol->GetValueOffset()) % cbAlignment ) );

                //
                // The status column is interesting for almost all columns,
                // even inline columns, since a summary catalog might have
                // VT_EMPTY data for these columns (eg storage props).
                //
                TableCol->SetStatusField( RowMap.AllocOffset( sizeof (BYTE),
                                                              sizeof (BYTE),
                                                              TRUE ),
                                          sizeof (BYTE));
            }
        }
#endif // ndef ALWAYS_USE_VARIANT_BINDING

        //
        //  If this is the row ID column, save its offset in the row.
        //
        if (rColumnInfo.dwFlags & DBCOLUMNFLAGS_ISROWID)
        {
#ifdef ALWAYS_USE_VARIANT_BINDING
            Win4Assert(TableCol->GetStoredType() == VT_VARIANT &&
                       TableCol->IsValueStored() &&
                       TableCol->GetValueSize() == sizeof (PROPVARIANT));
            PROPVARIANT prop;
            obRowId = TableCol->GetValueOffset() +
                       (DWORD)((BYTE *) &prop.lVal - (BYTE *)&prop);
#else // ndef ALWAYS_USE_VARIANT_BINDING
            Win4Assert(TableCol->GetStoredType() == VT_I4 &&
                       TableCol->IsValueStored() &&
                       TableCol->GetValueSize() == sizeof (ULONG));
            obRowId = TableCol->GetValueOffset();
#endif // ndef ALWAYS_USE_VARIANT_BINDING
            _iColRowId = iCol;
            fAddedWorkId = TRUE;
        }

        //
        //  If this is the chapter column, save its offset in the row.
        //
        if (rColumnInfo.dwFlags & DBCOLUMNFLAGS_ISCHAPTER)
        {
            Win4Assert( _fChaptered );
#ifdef ALWAYS_USE_VARIANT_BINDING
            Win4Assert(TableCol->GetStoredType() == VT_VARIANT &&
                       TableCol->IsValueStored() &&
                       TableCol->GetValueSize() == sizeof (PROPVARIANT));
            PROPVARIANT prop;
            obChaptId = TableCol->GetValueOffset() +
                       (DWORD)((BYTE *) &prop.lVal - (BYTE *)&prop);
#else // ndef ALWAYS_USE_VARIANT_BINDING
            Win4Assert(TableCol->GetStoredType() == VT_I4 &&
                       TableCol->IsValueStored() &&
                       TableCol->GetValueSize() == sizeof (ULONG));
            obChaptId = TableCol->GetValueOffset();
#endif // ndef ALWAYS_USE_VARIANT_BINDING
        }

        XColumns->Add(TableCol.GetPointer(), cBoundColumns++);
        TableCol.Acquire();
    }

    tbDebugOut(( DEB_ITRACE, "colinfo::set, after loop, cBoundColumns: %d\n",
                 cBoundColumns ));

    // Need to add workid for non-sequential queries so that bookmarks
    // work, and either workid or path so that deferred values work.

    if ( ( ( !_fSequential ) ||
           ( fMayDefer && rpQuery.CanDoWorkIdToPath() ) ) &&
         ( !fAddedWorkId ) )
    {
        tbDebugOut(( DEB_ITRACE, "colinfo::set, adding WID column\n" ));

        //
        // Need to add the row ID column to the bindings, so that bookmarks
        // work, and deferred values can be loaded.
        //
        const DBCOLUMNINFO & rColumnInfo = _GetRowIdColumnInfo( );

        unsigned iCol = _pidmap.NameToPid( rColumnInfo.columnid );
        XPtr<CTableColumn> TableCol(new CTableColumn( iCol ));
        _iColRowId = iCol;

        Win4Assert (VT_I4 == rColumnInfo.wType);

        if (sizeof (ULONG) > maxAlignment)
            maxAlignment = sizeof (ULONG);

        TableCol->SetValueField( VT_I4,
                                 RowMap.AllocOffset( sizeof (ULONG),
                                                     sizeof (ULONG),
                                                     TRUE ),
                                 sizeof (ULONG));
        obRowId = TableCol->GetValueOffset();

        TableCol->SetStatusField( RowMap.AllocOffset( sizeof (BYTE),
                                                      sizeof (BYTE),
                                                      TRUE ),
                                  sizeof (BYTE));

        XColumns->Add(TableCol.GetPointer(), cBoundColumns++);
        TableCol.Acquire();
    }

    ULONG rem = RowMap.GetRowWidth() % maxAlignment;

    if ( 0 == rem )
        _cbRowWidth = RowMap.GetRowWidth();
    else
        _cbRowWidth = RowMap.GetRowWidth() + maxAlignment - rem;

    rpQuery.SetBindings( hCursor,
                         _cbRowWidth,
                         XColumns.GetReference(),
                         _pidmap );

    tbDebugOut(( DEB_ITRACE, "colinfo::set, old # cols %d, new # cols %d\n",
                 _cColumns, cBoundColumns ));

    _cBoundColumns = cBoundColumns;
    _pColumns = XColumns.Acquire();
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::Get1ColumnInfo, public
//
//  Synopsis:   Return information about a single column in the rowset.
//
//  Arguments:  [iColumn] - the column number whose column info is to be
//                              returned.
//
//  Returns:    DBCOLUMNINFO & - a pointer to column info for the column
//
//  Notes:
//
//  History:    29 Mar 1995     AlanW   Created
//
//----------------------------------------------------------------------------

const DBCOLUMNINFO & CColumnsInfo::Get1ColumnInfo(
    ULONG iColumn
) /*const*/
{
    const CFullPropSpec & ColId = *_pidmap.Get(iColumn);

    return _FindColumnInfo( ColId );
}


#ifdef  INCLUDE_COLUMNS_ROWSET_IMPLEMENTATION
STDMETHODIMP CRowset::GetColumnsRowset(
    ULONG         cSelections,
    DBID          rgColumnSelection[],
    IRowset **    ppColCursor
) /*const*/ {
    _ErrorObject.ClearErrorInfo();
    return _ErrorObject.PostHResult(E_NOTIMPL, IID_IColumnsRowset);
}

STDMETHODIMP CRowset::GetAvailableColumns(
    ULONG *       pcSelections,
    DBID **       rgColumnSelection
) /*const*/ {
    _ErrorObject.ClearErrorInfo();
    return _ErrorObject.PostHResult(E_NOTIMPL, IID_IColumnsRowset);
}
#endif // INCLUDE_COLUMNS_ROWSET_IMPLEMENTATION



////////////////////////////////////////////////////////////
//
//  Data declarations for _FindColumnInfo
//
//  Notes:      These arrays of structures are prototype column info.
//              structures returned by _FindColumnInfo.
//
////////////////////////////////////////////////////////////

static const DBCOLUMNINFO aStoragePropDescs[] = {
    {   L"FileDirectoryName", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE, MAX_PATH, // storage props are never deferred
        DBTYPE_WSTR, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_DIRECTORY )  }
     },

    {   L"ClassID", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (GUID),
        DBTYPE_GUID, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_CLASSID ) }
    },

    {   L"FileStorageType", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (ULONG),
        DBTYPE_UI4, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_STORAGETYPE ) }
    },

    {   L"FileIndex", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONGLONG),
        DBTYPE_I8, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_FILEINDEX ) }
    },

    {   L"FileUSN", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONGLONG),
        DBTYPE_I8, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_LASTCHANGEUSN ) }
    },

    {   L"FileName", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE, MAX_PATH, // storage props are never deferred
        DBTYPE_WSTR, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_NAME ) }
    },

    {   L"FilePathName", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE, MAX_PATH, // storage props are never deferred
        DBTYPE_WSTR, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_PATH ) }
     },

    {   L"FileSize", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONGLONG),
        DBTYPE_I8, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_SIZE ) }
    },

    {   L"FileAttributes", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (ULONG),
        DBTYPE_UI4, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_ATTRIBUTES ) }
    },

// NOTE:  file times are typed as DBTYPE_DATE, but are bound to the
//          table as VT_FILETIME.
    {   L"FileWriteTime", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONGLONG),
        DBTYPE_DATE, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_WRITETIME ) }
    },

    {   L"FileCreateTime", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONGLONG),
        DBTYPE_DATE, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_CREATETIME ) }
    },

    {   L"FileAccessTime", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONGLONG),
        DBTYPE_DATE, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_ACCESSTIME ) }
    },

    {   L"FileShortName", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE, 13,   // storage props are never deferred
        DBTYPE_WSTR, 0xff, 0xff,
      { PSGUID_STORAGE, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PID_STG_SHORTNAME ) }
    },
};

const ULONG cStoragePropDescs =
                          sizeof aStoragePropDescs /
                          sizeof aStoragePropDescs[0];


//
//  Standard query properties.
//  Does not include pidAll or pidContent, those are used only in restrictions.
//

static const DBCOLUMNINFO aQueryPropDescs[] = {
    {   L"QueryRankvector", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof(PROPVARIANT),
        DBTYPE_VARIANT, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_RANKVECTOR ) }
    },

    {   L"QueryRank", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_RANK ) }
    },

    {   L"QueryHitCount", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_HITCOUNT ) }
    },

    {   L"WorkID", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH|DBCOLUMNFLAGS_ISROWID, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_WORKID ) }
    },

    {   L"QueryUnfiltered", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof(BOOL),
        DBTYPE_BOOL, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_UNFILTERED ) }
    },

    {   L"QueryVirtualPath", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE, MAX_PATH,
        DBTYPE_WSTR, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_VIRTUALPATH ) }
    },

#if defined( DISPID_QUERY_NLIRRANK )
    {   L"NLIRRank", 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE|DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBQUERYGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( DISPID_QUERY_NLIRRANK ) }
    },
#endif // defined( DISPID_QUERY_NLIRRANK )
};

const ULONG cQueryPropDescs =
                          sizeof aQueryPropDescs /
                          sizeof aQueryPropDescs[0];


static DBCOLUMNINFO const aBmkPropDescs[] = {
    {   L"Bookmark", 0, 0,
        DBCOLUMNFLAGS_ISBOOKMARK|DBCOLUMNFLAGS_ISFIXEDLENGTH,
        sizeof (CI_TBL_BMK),
        DBTYPE_BYTES, 0xff, 0xff,
      { DBBMKGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PROPID_DBBMK_BOOKMARK ) }
    },
    {   L"Chapter", 0, 0,
        DBCOLUMNFLAGS_ISCHAPTER|DBCOLUMNFLAGS_ISBOOKMARK|DBCOLUMNFLAGS_ISFIXEDLENGTH,
        sizeof (CI_TBL_CHAPT),
        DBTYPE_BYTES, 0xff, 0xff,
      { DBBMKGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PROPID_DBBMK_CHAPTER ) }
    },
};

const ULONG cBmkPropDescs =
                          sizeof aBmkPropDescs /
                          sizeof aBmkPropDescs[0];


// CLEANCODE: ole-db spec bug #1271 - const GUID init. less than useful

#ifndef DBSELFGUID
#define DBSELFGUID {0xc8b52231,0x5cf3,0x11ce,{0xad,0xe5,0x00,0xaa,0x00,0x44,0x77,0x3d}}
#endif // ndef DBSELFGUID

static DBCOLUMNINFO const aSelfPropDescs[] = {
    {   L"Self", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH,
        sizeof( int ),
        DBTYPE_I4, 0xff, 0xff,
      { DBSELFGUID, DBKIND_GUID_PROPID, (LPWSTR) UIntToPtr( PROPID_DBSELF_SELF ) }
    },
};

const ULONG cSelfPropDescs =
                          sizeof aSelfPropDescs /
                          sizeof aSelfPropDescs[0];

#ifdef  INCLUDE_COLUMNS_ROWSET_IMPLEMENTATION

static const DBCOLUMNINFO aColInfoPropDescs[] = {

    {   L"ColumnId", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, 3 * sizeof(PROPVARIANT),
        DBTYPE_VARIANT|DBTYPE_VECTOR, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)1 }
    },

    {   L"ColumnName", 0, 0,
        0, 20,
        DBTYPE_WSTR, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)2 }
    },

    {   L"ColumnNumber", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)3 }
    },

    {   L"ColumnType", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof(USHORT),
        DBTYPE_I2, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)4 }
    },

    {   L"ColumnLength", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)5 }
    },

    {   L"ColumnPrecision", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)6 }
    },

    {   L"ColumnScale", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)7 }
    },

    {   L"ColumnFlags", 0, 0,
        DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (LONG),
        DBTYPE_I4, 0xff, 0xff,
      { DBCIDGUID, DBKIND_GUID_PROPID, (LPWSTR)8 }
    },
};

const ULONG cColInfoPropDescs =
                          sizeof aColInfoPropDescs /
                          sizeof aColInfoPropDescs[0];
#endif // INCLUDE_COLUMNS_ROWSET_IMPLEMENTATION

//
//  Array of column descriptions per known propset
//  Each referenced array must have the same Guid for each element in
//  the array.
//

const CColumnsInfo::SPropSetInfo CColumnsInfo::aPropSets [ ] = {
#define IPROPSET_STORAGE        0       // Storage property set index
        { cStoragePropDescs,    aStoragePropDescs },
        { cQueryPropDescs,      aQueryPropDescs },
        { cBmkPropDescs,        aBmkPropDescs },
        { cSelfPropDescs,       aSelfPropDescs },

#ifdef  INCLUDE_COLUMNS_ROWSET_IMPLEMENTATION
        { cColInfoPropDescs,    aColInfoPropDescs },
#endif // INCLUDE_COLUMNS_ROWSET_IMPLEMENTATION
};

const ULONG CColumnsInfo::cPropSets =
                          sizeof CColumnsInfo::aPropSets /
                          sizeof CColumnsInfo::aPropSets[0];

DBCOLUMNINFO const DefaultColumnInfo = {
        0, 0, 0,
        DBCOLUMNFLAGS_ISNULLABLE | DBCOLUMNFLAGS_MAYDEFER | DBCOLUMNFLAGS_ISFIXEDLENGTH, sizeof (PROPVARIANT),
        DBTYPE_VARIANT, 0xff, 0xff,
      { {0,0,0,{0,0,0,0,0,0,0,0}}, DBKIND_GUID_PROPID, (LPWSTR)0 }
    };

//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::_FindColumnInfo, static
//
//  Synopsis:   Return information about a particular column ID.
//
//  Arguments:  [ColId] -     Column ID to be looked up.
//
//  Returns:    DBCOLUMNINFO - the column information for the row looked up.
//
//  Notes:      Some columns are standard columns available for all file
//              stores.  For these columns, full information about data
//              type and sizes can be returned.  For any other columns,
//              a generic column information structure is returned.
//
//  History:    10 Feb 1995     AlanW   Created
//
//----------------------------------------------------------------------------

DBCOLUMNINFO const & CColumnsInfo::_FindColumnInfo(
    const CFullPropSpec & ColId
) {
    DBCOLUMNINFO const * pColInfo = &DefaultColumnInfo;

    //
    //  All custom information we return has propids, not prop names.
    //  Valid property IDs start at 2
    //

    if (ColId.IsPropertyPropid())
    {
        for (unsigned iPropSet = 0; iPropSet < cPropSets; iPropSet++)
        {
            if (ColId.GetPropSet() ==
                aPropSets[iPropSet].aPropDescs[0].columnid.uGuid.guid)
            {
                //
                //  Found the guid for the propset, now try to find the
                //  propid.
                //
                ULONG ulPropId = ColId.GetPropertyPropid();

                Win4Assert( ulPropId != PID_CODEPAGE &&
                            ulPropId != PID_DICTIONARY);

                for (unsigned iDesc = 0;
                     iDesc < aPropSets[iPropSet].cProps;
                     iDesc++)
                {
                    if (ulPropId ==
                        aPropSets[iPropSet].aPropDescs[iDesc].columnid.uName.ulPropid)
                    {
                        pColInfo = &aPropSets[iPropSet].aPropDescs[iDesc];
                        break;
                    }
                }
                break;
            }
        }
    }
    return *pColInfo;
}


//+---------------------------------------------------------------------------
//
//  Member:     CColumnsInfo::_GetRowIdColumnInfo, static
//
//  Synopsis:   Return information about the row ID column
//
//  Arguments:  - None -
//
//  Returns:    DBCOLUMNINFO - the column information for the row looked up.
//
//  Notes:      It is assumed that there is only one row ID column in the
//              standard column info.  This may need to change for chaptered
//              rowsets.
//
//  History:    15 Mar 1995     AlanW   Created
//
//----------------------------------------------------------------------------

DBCOLUMNINFO const & CColumnsInfo::_GetRowIdColumnInfo(
) {
    DBCOLUMNINFO const * pColInfo = 0;

    for (unsigned iPropSet = 0;
         iPropSet < cPropSets && pColInfo == 0;
         iPropSet++)
    {
        for (unsigned iDesc = 0;
             iDesc < aPropSets[iPropSet].cProps;
             iDesc++)
        {
            if ( aPropSets[iPropSet].aPropDescs[iDesc].dwFlags &
                                     DBCOLUMNFLAGS_ISROWID)
            {
                pColInfo = &aPropSets[iPropSet].aPropDescs[iDesc];
                break;
            }
        }
    }

    Win4Assert(pColInfo != 0);
    return *pColInfo;
}