//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1995 - 2000.
//
//  File:       qryspec.cxx
//
//  Contents:   ICommandTree implementation for OFS file stores
//
//  Classes:    CRootQuerySpec
//
//  Functions:  CheckForErrors
//              CheckForPriorTree
//
//  History:    30 Jun 1995   AlanW     Created
//              10-31-97      danleg    Added ICommandText & ICommandPrepare
//
//----------------------------------------------------------------------------

#include "pch.cxx"
#pragma hdrstop

#include <colinfo.hxx>
#include <parstree.hxx>
#include <hraccess.hxx>
#include <mparser.h>
#include <propglob.hxx>
#include <doquery.hxx>
#include "qryspec.hxx"

// Command object Interfaces that support Ole DB error objects
static const IID * apCommandErrorIFs[] =
{
        &IID_IAccessor,
        &IID_IColumnsInfo,
        &IID_ICommand,
        &IID_ICommandProperties,
        &IID_ICommandText,
        &IID_IConvertType,
        //&IID_IColumnsRowset,
        &IID_ICommandPrepare,
        &IID_ICommandTree,
        //&IID_ICommandWithParameters,
        &IID_IQuery,
        //&IID_ISupportErrorInfo,
        &IID_IServiceProperties
};
static const ULONG cCommandErrorIFs  = sizeof(apCommandErrorIFs)/sizeof(apCommandErrorIFs[0]);

// SQL defining global views.  These views are defined at the datasrc level, if one is present,
// or at the command level otherwise.
extern const LPWSTR s_pwszPredefinedViews =
    L"SET GLOBAL ON; "
    L"CREATE VIEW FILEINFO "
    L"       AS SELECT PATH, FILENAME, SIZE, WRITE, ATTRIB FROM SCOPE(); "
    L"CREATE VIEW FILEINFO_ABSTRACT     "
    L"       AS SELECT PATH, FILENAME, SIZE, WRITE, ATTRIB, CHARACTERIZATION FROM SCOPE(); "
    L"CREATE VIEW EXTENDED_FILEINFO     "
    L"       AS SELECT PATH, FILENAME, SIZE, WRITE, ATTRIB, DOCTITLE, DOCAUTHOR, DOCSUBJECT, DOCKEYWORDS, CHARACTERIZATION FROM SCOPE(); "
    L"CREATE VIEW WEBINFO "
    L"       AS SELECT VPATH, PATH, FILENAME, SIZE, WRITE, ATTRIB, CHARACTERIZATION, DOCTITLE FROM SCOPE(); "
    L"CREATE VIEW EXTENDED_WEBINFO "
    L"       AS SELECT VPATH, PATH, FILENAME, SIZE, CHARACTERIZATION, WRITE, DOCAUTHOR, DOCSUBJECT, DOCKEYWORDS, DOCTITLE FROM SCOPE(); "
    L"CREATE VIEW SSWebInfo "
    L"       AS SELECT URL, DOCTITLE, RANK, SIZE, WRITE FROM SCOPE(); "
    L"CREATE VIEW SSExtended_WebInfo "
    L"       AS SELECT URL, DOCTITLE, RANK, HITCOUNT, DOCAUTHOR, CHARACTERIZATION, SIZE, WRITE FROM SCOPE()";

//+-------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::CRootQuerySpec, public
//
//  Synopsis:   Constructor of a CRootQuerySpec
//
//  Arguments:  [pOuterUnk] - Outer unknown
//              [ppMyUnk]   - OUT:  filled in with pointer to non-delegated
//                            IUnknown on return
//
//  History:    08-Feb-96   KyleP    Added support for virtual paths
//
//--------------------------------------------------------------------------

CRootQuerySpec::CRootQuerySpec (IUnknown *          pOuterUnk,
                                IUnknown **         ppMyUnk,
                                CDBSession *        pSession)
        : _dwDepth(QUERY_SHALLOW),
          _pInternalQuery(0),
          _pQueryTree(0),
          _pColumnsInfo(0),
#pragma warning(disable : 4355) // 'this' in a constructor
         _impIUnknown(this),
         _aAccessors( (IUnknown *) (ICommand *)this ),
         _DBErrorObj( * ((IUnknown *) (ICommand *) this), _mtxCmd ),
#pragma warning(default : 4355)    // 'this' in a constructor
          _dwStatus(0),
          _guidCmdDialect(DBGUID_SQL),
          _fGenByOpenRowset(FALSE),
          _pwszSQLText(0),
          _RowsetProps( pSession ?
                        pSession->GetDataSrcPtr()->GetDSPropsPtr()->
                            GetValLong( CMDSProps::eid_DBPROPSET_DBINIT,
                                    CMDSProps::eid_DBPROPVAL_INIT_LCID ) :
                        0 ),
          _PropInfo()

{
    if (pOuterUnk)
        _pControllingUnknown = pOuterUnk;
    else
        _pControllingUnknown = (IUnknown * )&_impIUnknown;

    _DBErrorObj.SetInterfaceArray(cCommandErrorIFs, apCommandErrorIFs);

    if ( pSession )
    {
        _xSession.Set( pSession );
        _xSession->AddRef();

        _xpIPSession.Set( pSession->GetParserSession() );

        //
        // The above Set() doesn't AddRef. This will balance the XInterface<>
        // dtor Release
        //
        _xpIPSession->AddRef();
    }

    *ppMyUnk = ((IUnknown *)&_impIUnknown);
    (*ppMyUnk)->AddRef();
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::CRootQuerySpec, public
//
//  Synopsis:   Copy constructor
//
//  Arguments:  [src] -- Source query spec
//
//  History:    27 Jun 95   AlanW       Created
//              10 Jan 98   danleg      Replaced body with Assert
//
//----------------------------------------------------------------------------

CRootQuerySpec::CRootQuerySpec( CRootQuerySpec & src )
        : _dwDepth( src._dwDepth ),
          _pInternalQuery(0),
          _pQueryTree(0),
          _pColumnsInfo(0),
#pragma warning(disable : 4355) // 'this' in a constructor
         _impIUnknown(this),
         _aAccessors( (IUnknown *) (ICommand *)this ),
         _DBErrorObj( * ((IUnknown *) (ICommand *) this), _mtxCmd ),
#pragma warning(default : 4355)    // 'this' in a constructor
          _dwStatus(src._dwStatus),
          _guidCmdDialect(src._guidCmdDialect),
          _fGenByOpenRowset(src._fGenByOpenRowset),
          _pwszSQLText(0),
          _RowsetProps( src._RowsetProps ),
          _PropInfo()
{
    Win4Assert( !"CRootQuerySpec copy constructor not implemented.");
}

//+-------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::~CRootQuerySpec, private
//
//  Synopsis:   Destructor of a CRootQuerySpec
//
//--------------------------------------------------------------------------

CRootQuerySpec::~CRootQuerySpec()
{
    ReleaseInternalQuery();

    delete    _pColumnsInfo;
    delete    _pQueryTree;
    delete [] _pwszSQLText;
}


//+-------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::RealQueryInterface, public
//
//  Synopsis:   Get a reference to another interface on the cursor.  AddRef
//              is done in CImpIUnknown::QueryInterface
//
//  History:    10-31-97    danleg      Added ICommandText& ICommandPrepare
//
//--------------------------------------------------------------------------

//
// Hack #214: IID_ICommandProperties is intercepted by service layers, which
//            don't like us passing in the magic code to fetch hidden scope
//            properties.  But the controlling unknown doesn't recognize
//            IID_IKyleProp and sends it right to us.  Implementation is
//            identical to ICommandProperties.
//

extern GUID IID_IKyleProp;

SCODE CRootQuerySpec::RealQueryInterface(
    REFIID ifid,
    void * *ppiuk )
{
    SCODE sc = S_OK;

    TRY
    {
        // validate the param before any addrefs
        *ppiuk = 0;

        // note -- IID_IUnknown covered in QueryInterface

        if ( IID_ICommand == ifid )
        {
            *ppiuk = (void *) ((ICommand *) this);
        }
        else if (IID_ISupportErrorInfo == ifid)
        {
            *ppiuk = (void *) ((IUnknown *) (ISupportErrorInfo *) &_DBErrorObj);
        }
        else if ( IID_IAccessor == ifid )
        {
            *ppiuk = (void *) (IAccessor *) this;
        }
        else if ( IID_IColumnsInfo == ifid )
        {
           *ppiuk = (void *) (IColumnsInfo *) GetColumnsInfo();
        }
    // NTRAID#DB-NTBUG9-84306-2000/07/31-dlee OLE-DB spec variance in Indexing Service, some interfaces not implemented
    #if 0  
        else if ( IID_IRowsetInfo == ifid )
        {
            *ppiuk = (void *) (IRowsetInfo *) this;
        }
    #endif // 0
        else if ( IID_ICommandTree == ifid )
        {
            *ppiuk = (void *) (ICommandTree *) this;
        }
    // NTRAID#DB-NTBUG9-84306-2000/07/31-dlee OLE-DB spec variance in Indexing Service, some interfaces not implemented
    #if 0 
        else if ( IID_ICommandValidate == ifid )
        {
            *ppiuk = (void *) (ICommandValidate *) this;
        }
    #endif // 0
        else if ( IID_IQuery == ifid )
        {
            *ppiuk = (void *) (IQuery *) this;
        }
        else if ( IID_ICommandProperties == ifid || IID_IKyleProp == ifid )
        {
            *ppiuk = (void *) (ICommandProperties *) this;
        }
        else if ( IID_IServiceProperties == ifid )
        {
            *ppiuk = (void *) (IServiceProperties *) this;
        }
        else if ( IID_IConvertType == ifid )
        {
            *ppiuk = (void *) (IConvertType *) this;
        }
        else if ( IID_ICommandText == ifid )
        {
            // Create parser sesson object if deferred during construction
            if ( _xpIPSession.IsNull() )
                CreateParser();

            *ppiuk = (void *) (ICommandText *) this;
        }
        else if ( IID_ICommandPrepare == ifid )
        {
            *ppiuk = (void *) (ICommandPrepare *) this;
        }

        else
        {
            *ppiuk = 0;
            sc = E_NOINTERFACE;
        }
    }
    CATCH( CException, e )
    {
        vqDebugOut(( DEB_ERROR, "Exception %08x while doing QueryInterface \n",
                     e.GetErrorCode() ));
        sc = GetOleError(e);
    }
    END_CATCH

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::FindErrorNodes, public
//
//  Synopsis:   Find error nodes in a command tree
//
//  Arguments:  [pRoot]         -- DBCOMMANDTREE node at root of tree
//              [pcErrorNodes]  -- pointer where count of error nodes is ret'd
//
//  History:    27 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

BOOL CheckForErrors( const CDbCmdTreeNode *pNode )
{
    if (pNode->GetError() != S_OK)
        return TRUE;
    else
        return FALSE;
}


SCODE CRootQuerySpec::FindErrorNodes(
    const DBCOMMANDTREE *  pRoot,
    ULONG *                pcErrorNodes,
    DBCOMMANDTREE ***      prgErrorNodes)
{
    SCODE sc = S_OK;

    ULONG cErrors = 0;
    XArrayOLE<DBCOMMANDTREE *> pErrorNodes;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        _FindTreeNodes( CDbCmdTreeNode::CastFromStruct(pRoot),
                        cErrors,
                        pErrorNodes,
                        CheckForErrors );

        *pcErrorNodes = cErrors;
        *prgErrorNodes = pErrorNodes.GetPointer();
        pErrorNodes.Acquire();
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_ICommandTree );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::_FindTreeNodes, private
//
//  Synopsis:   Find nodes in a command tree meeting some condition
//
//  Arguments:  [pRoot]         -- DBCOMMANDTREE node at root of tree
//              [rcMatchingNodes]  -- count of matching nodes returned
//              [prgMatchingNodes] -- pointer to array of nodes returned
//              [pfnCheckNode]  -- function which returns true if a tree
//                                 node matches desired condition.
//              [iDepth]        -- depth of tree; for detecting cycles
//
//  Notes:      In order to avoid looping endlessly over a tree with cycles,
//              this routine will bail out if the tree depth is greater
//              than 1000 or if the tree width is greater than 100,000.
//
//              We don't expect this routine to be called in situations
//              where it will return very large numbers of tree nodes,
//              so we grow the returned array only one element at a time.
//
//  History:    27 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

const unsigned MAX_TREE_DEPTH = 1000;   // max tree depth
const unsigned MAX_TREE_WIDTH = 100000; // max tree width


void CRootQuerySpec::_FindTreeNodes(
    const CDbCmdTreeNode * pRoot,
    ULONG &                rcMatchingNodes,
    XArrayOLE<DBCOMMANDTREE *> & rpMatchingNodes,
    PFNCHECKTREENODE       pfnCheckNode,
    unsigned               iDepth)
{
    if (iDepth > MAX_TREE_DEPTH)
        THROW(CException(E_FAIL));

    unsigned iWidth = 0;

    while (pRoot)
    {
        if (pRoot->GetFirstChild())
            _FindTreeNodes( pRoot->GetFirstChild(),
                            rcMatchingNodes,
                            rpMatchingNodes,
                            pfnCheckNode,
                            iDepth+1);

        if (iWidth > MAX_TREE_WIDTH)
            THROW(CException(E_FAIL));

        if ((pfnCheckNode)(pRoot))
        {
            XArrayOLE<DBCOMMANDTREE *> pMatchTemp( rcMatchingNodes+1 );
            if (0 == pMatchTemp.GetPointer())
                THROW(CException(E_OUTOFMEMORY));

            if (rcMatchingNodes > 0)
            {
                Win4Assert(rpMatchingNodes.GetPointer() != 0);
                RtlCopyMemory(pMatchTemp.GetPointer(),
                              rpMatchingNodes.GetPointer(),
                              sizeof (DBCOMMANDTREE*) * rcMatchingNodes);
                CoTaskMemFree(rpMatchingNodes.Acquire());
            }
            (pMatchTemp.GetPointer())[rcMatchingNodes] = pRoot->CastToStruct();
            rcMatchingNodes++;

            rpMatchingNodes.Set( rcMatchingNodes, pMatchTemp.Acquire() );
        }

        pRoot = pRoot->GetNextSibling();
        iWidth++;
    }
    return;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::FreeCommandTree, public
//
//  Synopsis:   Free a command tree
//
//  Arguments:  [ppRoot]   -- DBCOMMANDTREE node at root of tree to be freed
//
//  History:    27 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::FreeCommandTree(
    DBCOMMANDTREE * *     ppRoot)
{
    SCODE sc = S_OK;

    if ( 0 == ppRoot )
        return E_INVALIDARG;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        CDbCmdTreeNode * pCmdTree = (CDbCmdTreeNode *)
                          CDbCmdTreeNode::CastFromStruct(*ppRoot);

        //
        //  If the user tries to delete our query tree, zero our pointer to it.
        //
        //  NOTE: Nothing prevents the user from freeing a subtree of
        //        our tree if they called SetCommandTree with fCopy FALSE.
        //

        // There is a proposed spec change on this.  According to the current spec,
        // if fCopy was FALSE we need to return DB_E_CANNOTFREE  here. (MDAC BUGG# 6386)
        if ( _dwStatus & CMD_OWNS_TREE )
        {
            THROW( CException(DB_E_CANNOTFREE) );
        }
        else
        {
            if ( pCmdTree == _pQueryTree )
            _pQueryTree = 0;

            delete pCmdTree;
            *ppRoot = 0;
        }
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_ICommandTree );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetCommandTree, public
//
//  Synopsis:   Get a copy of the command tree.
//
//  Arguments:  [ppRoot]  -- pointer to where DBCOMMANDTREE is returned
//
//  History:    27 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::GetCommandTree(
    DBCOMMANDTREE * *     ppRoot)
{
    if ( 0 == ppRoot )
        return _DBErrorObj.PostHResult( E_INVALIDARG, IID_ICommandTree );

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        //
        // Initialize return parameter
        //
        *ppRoot = 0;

        if ( 0 == _pQueryTree )
        {
            //
            // Build it if we have a command text
            //
            if ( IsCommandSet() )
            {
                sc = BuildTree( );

                // SET PROPERTYNAME ... query
                if ( DB_S_NORESULT == sc )
                    return S_OK;

                _dwStatus |= CMD_TREE_BUILT;
            }

            //
            // The command text didn't generate a tree  (i.e. it was
            // either a CREATE VIEW or SET PROPERTYNAME ) or a
            // command text wasn't set
            //
            if ( 0 == _pQueryTree )
            {
                return S_OK;
            }
        }

        XPtr<CDbCmdTreeNode> TreeCopy( _pQueryTree->Clone(TRUE) );
        if (0 == TreeCopy.GetPointer())
            THROW(CException(E_OUTOFMEMORY));

        *ppRoot = TreeCopy.GetPointer()->CastToStruct();
        TreeCopy.Acquire();
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_ICommandTree );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::SetCommandTree, public
//
//  Synopsis:   Set the command tree.
//
//  Arguments:  [ppRoot]   -- pointer to DBCOMMANDTREE to be set in command obj
//              [dwCommandReuse] -- indicates whether state is retained.
//              [fCopy]   -- if TRUE, a copy of ppRoot is made.  Otherwise,
//                           ownership of the command tree passes to the
//                           command object.
//
//  History:    27 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::SetCommandTree(
    DBCOMMANDTREE * *       ppRoot,
    DBCOMMANDREUSE          dwCommandReuse,
    BOOL                    fCopy)
{
    if ( HaveQuery() && _pInternalQuery->IsQueryActive() )
        return DB_E_OBJECTOPEN;

    if ( 0 == ppRoot )
        return E_INVALIDARG;

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        _CheckRootNode( *ppRoot );

        if ( 0 != _pQueryTree )
        {
            delete _pQueryTree;
            _pQueryTree = 0;
        }

        CDbCmdTreeNode const * pCmdTree = CDbCmdTreeNode::CastFromStruct(*ppRoot);

        if ( FALSE == fCopy )
        {
            _dwStatus |= CMD_OWNS_TREE;
            _pQueryTree = (CDbCmdTreeNode *)pCmdTree;
            *ppRoot = 0;
        }
        else
        {
            _pQueryTree = pCmdTree->Clone();

            //
            // If Clone() fails it cleans up after itself and returns 0
            //

            if ( 0 == _pQueryTree )
                THROW( CException( E_OUTOFMEMORY ) );
        }

        _dwStatus &= ~CMD_COLINFO_NOTPREPARED;

        if ( _pColumnsInfo )
            InitColumns();

        //
        // If this is not being called internally (from BuildTree) remove the
        // the command text
        //
        if ( _dwStatus & CMD_TEXT_TOTREE )
        {
            _dwStatus &= ~CMD_TEXT_TOTREE;
        }
        else
        {
            delete [] _pwszSQLText;
            _pwszSQLText     = 0;
            _guidCmdDialect  = DBGUID_SQL;
            _dwStatus       &= ~CMD_TEXT_SET;
        }
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_ICommandTree );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::Execute, public
//
//  Synopsis:   Execute the command; create rowsets for the query resu.
//
//  Arguments:  [pOuterUnk]    -- controlling IUnknown for the rowset
//              [riid]         -- interface IID requested for the rowset
//              [pParams]      -- parameters for the query
//              [pcRowsAffected] -- returned count of affected rows
//              [ppRowset]     -- returned rowset
//
//  History:    27 Jun 95   AlanW       Created
//              11-20-97    danleg      Added ICommandText & ICommandPrepare
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::Execute(
    IUnknown *             pOuterUnk,
    REFIID                 riid,
    DBPARAMS *             pParams,
    DBROWCOUNT *           pcRowsAffected,
    IUnknown * *           ppRowset)
{

    _DBErrorObj.ClearErrorInfo();

    //
    // Called from OpenRowset?
    //
    GUID guidPost = (_fGenByOpenRowset) ? IID_IOpenRowset : IID_ICommand;

    if (0 == ppRowset && IID_NULL != riid )
        return _DBErrorObj.PostHResult( E_INVALIDARG, guidPost );

    if (0 != pOuterUnk && riid != IID_IUnknown)
        return _DBErrorObj.PostHResult( DB_E_NOAGGREGATION, guidPost );

    CLock lck( _mtxCmd );

    SCODE       scResult = S_OK;
    IRowset *   pIRowset = 0;

    _dwStatus &= ~(CMD_EXEC_RUNNING);

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        if ( ppRowset )
            *ppRowset = 0;

        // Impersonate the session logon user
        
        HANDLE hToken = INVALID_HANDLE_VALUE;
        
        if ( !_xSession.IsNull() )
             hToken = _xSession->GetLogonToken();

        CImpersonateSessionUser imp( hToken );

        // Either SetCommandTree or SetCommandText should have already been called

        if ( 0 == _pQueryTree )
        {
            if ( IsCommandSet() )
            {
                //
                // No query tree.  Build one from the command text if one has been set.
                //
                scResult = BuildTree( );

                if ( DB_S_NORESULT == scResult )
                    return scResult;

                _dwStatus |= CMD_TREE_BUILT;
            }
            else
                THROW( CException(DB_E_NOCOMMAND) );
        }

        _dwStatus |= CMD_EXEC_RUNNING;

        //
        // Convert the tree into restriction, etc.
        // The pParams should probably be passed to the ctor of
        // the parser (when we do parameterized queries).
        //
        CParseCommandTree   Parse;
        Parse.ParseTree( _pQueryTree );

        XGrowable<const WCHAR *,SCOPE_COUNT_GROWSIZE> xaScopes( SCOPE_COUNT_GROWSIZE );
        XGrowable<ULONG,SCOPE_COUNT_GROWSIZE>         xaFlags( SCOPE_COUNT_GROWSIZE );
        XGrowable<const WCHAR *,SCOPE_COUNT_GROWSIZE> xaCatalogs( SCOPE_COUNT_GROWSIZE );
        XGrowable<const WCHAR *,SCOPE_COUNT_GROWSIZE> xaMachines( SCOPE_COUNT_GROWSIZE );
        unsigned cScopes = 0;

        Parse.GetScopes( cScopes, xaScopes, xaFlags, xaCatalogs, xaMachines );
        
        // If the tree had a DBOP_tree node instead of a DBOP_content_table, we can't
        // parse scope information from the tree. The client is responsible for 
        // setting scope properties.  Currently, this means the Tripolish parser
        if ( 0 < cScopes )
        {
            SetScopeProperties( this,
                                cScopes,
                                xaScopes.Get(),
                                xaFlags.Get(),
                                xaCatalogs.Get(),
                                xaMachines.Get() );
        }

        CRestriction * pCrst = Parse.GetRestriction();
        CCategorizationSet & categ = Parse.GetCategorization();

        unsigned cRowsets = 1;
        if ( 0 != categ.Count() )
            cRowsets += categ.Count();

        if (Parse.GetOutputColumns().Count() == 0)
            THROW( CException(DB_E_ERRORSINCOMMAND) );

        XArray<IUnknown *> Unknowns( cRowsets );

        //
        // If it appears the server went down between the time we made
        // the connection and did the execute, attempt once to
        // re-establish the connection.
        //

        int cTries = 1;
        XPtr<CMRowsetProps> xProps;

        do
        {
            //
            // Use a copy of the properties so the command object isn't
            // affected by the implied properties.  xProps may be acquired
            // in a failed loop if the server disconnects just before the
            // setbindings call.
            //

            if ( xProps.IsNull() )
            {
                xProps.Set( new CMRowsetProps( _RowsetProps ) );
        
                xProps->SetImpliedProperties( riid, cRowsets );
        
                //
                // Check if there are any properties in error. If properties are found
                // to be in error, indicate this on _RowsetProps.
                //
                scResult = xProps->ArePropsInError( _RowsetProps );
        
                if ( S_OK != scResult )
                    return scResult;
        
                if ( Parse.GetMaxResults() > 0 )
                    xProps->SetValLong( CMRowsetProps::eid_DBPROPSET_ROWSET,
                                        CMRowsetProps::eid_PROPVAL_MAXROWS,
                                        Parse.GetMaxResults() );
                
                if ( Parse.GetFirstRows() > 0 )
                    xProps->SetFirstRows( Parse.GetFirstRows() );
            }

            if ( !HaveQuery() )
                _pInternalQuery = QueryInternalQuery();

            SCODE scEx = S_OK;

            TRY
            {
                //
                // Used for GetSpecification on the rowset
                //
                IUnknown * pCreatorUnk = 0;
                if ( IsGenByOpenRowset() )
                {
                    Win4Assert( !_xSession.IsNull() );
                    pCreatorUnk = _xSession->GetOuterUnk();
                }
                else
                {
                    pCreatorUnk = (IUnknown *) _pControllingUnknown;
                }

                Win4Assert( 0 != xProps.GetPointer() );

                _pInternalQuery->Execute( pOuterUnk,
                                          pCrst ? pCrst->CastToStruct() : 0,  // Restrictions
                                          Parse.GetPidmap(),
                                          Parse.GetOutputColumns(), // Output columns
                                          Parse.GetSortColumns(),   // Sort Order
                                          xProps,
                                          categ,                    // Categorization
                                          cRowsets,
                                          Unknowns.GetPointer(),    // Return interfaces
                                          _aAccessors,
                                          pCreatorUnk);
            }
            CATCH( CException, e )
            {
                scEx = e.GetErrorCode();
                if ( ( STATUS_CONNECTION_DISCONNECTED != scEx ) ||
                     ( cTries > 1 ) )
                    RETHROW();
            }
            END_CATCH;

            if ( STATUS_CONNECTION_DISCONNECTED == scEx )
            {
                Win4Assert( 1 == cTries );
                cTries++;
                ReleaseInternalQuery();
                continue;
            }
            else
            {
                Win4Assert( S_OK == scEx );
                break;
            }
        }
        while ( TRUE );

        // release these categorized rowsets -- they are addref'ed when the
        // client does a getreferencedrowset

        for ( unsigned x = 1; x < cRowsets; x++ )
            Unknowns[ x ]->Release();

        XInterface<IUnknown> xUnknown( Unknowns[0] );

        if (IID_IUnknown == riid)
        {
            *ppRowset = xUnknown.GetPointer();
            xUnknown.Acquire();
        }
        else
        {
            if (IID_NULL != riid)
                scResult = xUnknown->QueryInterface( riid, (void **)ppRowset );

            Win4Assert( S_OK == scResult );     // should have failed earlier
            if (FAILED(scResult))
                THROW( CException(scResult) );
        }

        _dwStatus &= ~(CMD_EXEC_RUNNING);

        imp.Revert();
    }
    CATCH( CException, e )
    {
        //
        // Can't use PostHResult( e...) here because the final SCODE may get translated
        //
        scResult = e.GetErrorCode();

        vqDebugOut(( DEB_ERROR, "Exception %08x while creating query\n", scResult ));

        if ( QUERY_E_INVALIDRESTRICTION == scResult )
            scResult = DB_E_ERRORSINCOMMAND;

        //
        // In the case of OpenRowset, don't want to Post DB_E_ERRORSINCOMMAND
        //
        if ( _fGenByOpenRowset )
        {
            if( scResult == DB_E_ERRORSINCOMMAND )
                scResult = DB_E_NOTABLE;
        }

       _DBErrorObj.PostHResult( scResult, guidPost );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    #if CIDBG == 1
        if ( ( S_OK == scResult ) && ( IID_NULL != riid ) )
            Win4Assert( 0 != *ppRowset );
    #endif

    return scResult;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::Cancel, public
//
//  Synopsis:   The consumer can allocate a secondary thread in which to cancel
//              the currently executing thread.  This cancel will only succeed
//              if the result set is still being generated.  If the rowset
//              object is being created, then it will be to late to cancel.
//
//  History:    11-20-97    danleg      Created
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::Cancel( void )
{
    _DBErrorObj.ClearErrorInfo();

    if( 0 == (_dwStatus & CMD_EXEC_RUNNING) )
        return S_OK;

    return _DBErrorObj.PostHResult(DB_E_CANTCANCEL, IID_ICommand);
}


#if 0 // ICommandValidate not yet implemented
//
//  ICommandValidate methods
//
SCODE CRootQuerySpec::ValidateCompletely( void )
{
    _DBErrorObj.ClearErrorInfo();
    vqDebugOut(( DEB_WARN, "CRootQuerySpec::ValidateCompletely not implemented\n" ));
    return PostHResult(E_NOTIMPL, IID_ICommandValidate);
}

SCODE CRootQuerySpec::ValidateSyntax( void )
{
    _DBErrorObj.ClearErrorInfo();
    vqDebugOut(( DEB_WARN, "CRootQuerySpec::ValidateSyntax not implemented\n" ));
    return PostHResult(E_NOTIMPL, IID_ICommandValidate);
}
#endif // 0


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetDBSession, public
//
//  Synopsis:   Return the session object associated with this command
//
//  Arguments:  [riid]      -- IID of the desired interface
//              [ppSession] -- pointer to where to return interface pointer
//
//  History:    11-20-97    danleg      Created
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::GetDBSession(
    REFIID      riid,
    IUnknown ** ppSession )
{
    _DBErrorObj.ClearErrorInfo();

    if (0 == ppSession)
        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_ICommand);

    SCODE   sc = S_OK;

    if ( !_xSession.IsNull() )
    {
        sc = (_xSession->GetOuterUnk())->QueryInterface( riid, (void **) ppSession );
    }
    else  // there was no session object
    {
        *ppSession = 0;
        sc = S_FALSE;
    }

    return sc;
}

//
// ICommandText methods
//

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetCommandText, public
//
//  Synopsis:   Echos the current command as text, including all
//              post-processing operations added.
//
//  Arguments:  [pguidDialect] -- Guid denoting the dialect of SQL
//              [ppwszCommand] -- Pointer to mem where to return command text
//
//  History:    10-01-97   danleg       Created from Monarch
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::GetCommandText
    (
    GUID *      pguidDialect,       //@parm INOUT | Guid denoting the dialect of sql
    LPOLESTR *  ppwszCommand        //@parm OUT | Pointer for the command text
    )
{
    SCODE       sc = S_OK;
    BOOL        fpguidNULL = FALSE;
    GUID        guidDialect;

    _DBErrorObj.ClearErrorInfo();

    CLock lck( _mtxCmd );

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        if( 0 == ppwszCommand )
        {
            THROW( CException(E_INVALIDARG) );
        }
        else
        {
            *ppwszCommand = 0;

            // Substitute a correct GUID for a NULL pguidDialect
            if( !pguidDialect )
            {
                guidDialect = DBGUID_SQL;
                pguidDialect = &guidDialect;

                // Don't return DB_S_DIALECTIGNORED in this case...
                fpguidNULL = TRUE;
            }

            // If the command has not been set, make sure the buffer
            // contains an empty string to return to the consumer
            if( !IsCommandSet() )
            {
                THROW( CException(DB_E_NOCOMMAND) );

            }
            else
            {
                // Allocate memory for the string we're going to return to the caller
                XArrayOLE<WCHAR> xwszCommand( wcslen(_pwszSQLText) + 1 ) ;

                // Copy our saved text into the newly allocated string
                wcscpy(xwszCommand.GetPointer(), _pwszSQLText);

                // If the text we're giving back is a different dialect than was
                // requested, let the caller know what dialect the text is in
                if( !fpguidNULL && _guidCmdDialect != *pguidDialect )
                {
                    *pguidDialect = _guidCmdDialect;
                    sc = DB_S_DIALECTIGNORED;
                }
                *ppwszCommand = xwszCommand.Acquire();
            }
        }
    }
    CATCH( CException, e )
    {
        sc  = _DBErrorObj.PostHResult( e, IID_ICommandText );
        if( pguidDialect )
            RtlZeroMemory( pguidDialect, sizeof(GUID) );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::SetCommandText, public
//
//  Synopsis:   Sets the current command text..
//
//  Arguments:  [rguidDialect] -- Guid denoting the dialect of SQL
//              [pwszCommand]  -- Command Text
//
//  History:    10-01-97   danleg       Created from Monarch
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::SetCommandText
    (
    REFGUID     rguidDialect,
    LPCOLESTR   pwszCommand
    )
{
    SCODE sc = S_OK;

    // Clear previous Error Object for this thread
    _DBErrorObj.ClearErrorInfo();

    CLock lck( _mtxCmd );

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        // Don't allow text to be set if we've got a rowset open
        if( !IsRowsetOpen() )
        {
            // Check Dialect
            if( rguidDialect == DBGUID_SQL || rguidDialect == DBGUID_DEFAULT )
            {
                //
                // Delete existing SQL text
                //
                delete [] _pwszSQLText;
                _pwszSQLText = 0;

                //
                // Delete Command Tree
                //
                delete _pQueryTree;
                _pQueryTree = 0;

                if( (0 == pwszCommand) || (L'\0' == *pwszCommand) )
                {
                    _guidCmdDialect  = DBGUID_SQL;
                    _dwStatus       &= ~CMD_TEXT_SET;
                    _dwStatus       &= ~CMD_COLINFO_NOTPREPARED;
                }
                else
                {
                    //
                    // Save the text and dialect
                    //
                    XArray<WCHAR> xwszSQLText( wcslen(pwszCommand) + 1 );
                    wcscpy(xwszSQLText.GetPointer(), pwszCommand);
                    _pwszSQLText = xwszSQLText.Acquire();

                    _guidCmdDialect = rguidDialect;

                    // Set status flag that we have set text
                    _dwStatus      |= CMD_TEXT_SET;
                    _dwStatus      |= CMD_COLINFO_NOTPREPARED;
                }

                if ( _pColumnsInfo )
                    InitColumns( );

                _dwStatus &= ~CMD_TEXT_PREPARED;
                _dwStatus &= ~CMD_TREE_BUILT;

                // Whenever new text is set on the Command Object,
                // the value for QUERY_RESTRICTION should be set to
                // an empty string

                _RowsetProps.SetValString(
                    CMRowsetProps::eid_DBPROPSET_MSIDXS_ROWSET_EXT,
                    CMRowsetProps::eid_MSIDXSPROPVAL_QUERY_RESTRICTION,
                    L"");
            }
            else
            {
                THROW( CException(DB_E_DIALECTNOTSUPPORTED) );
            }
        }
        else
        {
            THROW( CException(DB_E_OBJECTOPEN) );
        }
    }
    CATCH( CException, e )
    {
        sc  = _DBErrorObj.PostHResult( e, IID_ICommandText );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//
// ICommandPrepare methods
//
//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::Prepare, public
//
//  Synopsis:   Given that a SQL text has been set, prepare the statement
//
//  History:    10-31-97   danleg       Created from Monarch
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::Prepare
    (
    ULONG     cExpectedRuns
    )
{
    SCODE sc = S_OK;

    // Clear previous Error Object for this thread
    _DBErrorObj.ClearErrorInfo();

    CLock lck( _mtxCmd );

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        // Don't allow a new prepare with an open rowset
        if( !IsRowsetOpen() )
        {
            if( IsCommandSet() )
            {
                //
                // Don't build the tree again if it was built as a result of
                // GetCommandTree or Execute, and we haven't done a SetCommandText
                // since.
                //
                if ( !(_dwStatus & CMD_TREE_BUILT) )
                {
                    // Impersonate the session logon user

                    HANDLE hToken = INVALID_HANDLE_VALUE;

                    if ( !_xSession.IsNull() )
                         hToken = _xSession->GetLogonToken();

                    CImpersonateSessionUser imp( hToken );

                    sc = BuildTree( );

                    if ( DB_S_NORESULT == sc )
                        return sc;

                    _dwStatus |= CMD_TEXT_PREPARED;

                    if ( _pColumnsInfo )
                        InitColumns();

                    imp.Revert();
                }

            }
            else
                sc = DB_E_NOCOMMAND;
        }
        else
        {
            sc = DB_E_OBJECTOPEN;
        }
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_ICommandText );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::Unprepare, public
//
//  Synopsis:   Unprepare the current prepared command plan, if there is one.
//
//  History:    10-31-97   danleg       Created from Monarch
//
//----------------------------------------------------------------------------
STDMETHODIMP CRootQuerySpec::Unprepare
    (
    )
{
    SCODE sc = S_OK;

    // Clear previous Error Object for this thread
    _DBErrorObj.ClearErrorInfo();

    CLock lck( _mtxCmd );

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        if( !IsRowsetOpen() )
        {
            _dwStatus &= ~CMD_TEXT_PREPARED;
            _dwStatus |= CMD_COLINFO_NOTPREPARED;

            if ( _pColumnsInfo )
                InitColumns( );
        }
        else
            THROW( CException(DB_E_OBJECTOPEN) );
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_ICommandPrepare );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}


//
//  IQuery methods
//

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::AddPostProcessing, public
//
//  Synopsis:   Add to the top of a command tree
//
//  Arguments:  [ppRoot] -- DBCOMMANDTREE node at root of tree
//              [fCopy]  - TRUE if command tree should be copied
//
//  History:    29 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

BOOL CheckForPriorTree( const CDbCmdTreeNode *pNode )
{
    return pNode->GetCommandType() == DBOP_prior_command_tree;
}

SCODE CRootQuerySpec::AddPostProcessing(
    DBCOMMANDTREE * *      ppRoot,
    BOOL                   fCopy)
{
    _DBErrorObj.ClearErrorInfo();

    // OLEDB spec. bug #????; fCopy is non-sensical
    if (0 == ppRoot || FALSE == fCopy)
        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IQuery);

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        _CheckRootNode( *ppRoot );

        XPtr<CDbCmdTreeNode> TreeCopy( CDbCmdTreeNode::CastFromStruct(*ppRoot)->Clone() );
        if (0 == TreeCopy.GetPointer())
            THROW(CException(E_OUTOFMEMORY));

        ULONG cNodes = 0;
        XArrayOLE<DBCOMMANDTREE *> pNodes;

        _FindTreeNodes( TreeCopy.GetPointer(),
                        cNodes,
                        pNodes,
                        CheckForPriorTree );

        if (cNodes != 1)
        {
            vqDebugOut((DEB_WARN, "CRootQuerySpec::AddPostProcessing - "
                                   "%d references to prior tree found\n",
                                cNodes));
            THROW(CException(E_INVALIDARG));    // DB_E_BADCOMMANDTREE???
        }

        //
        //  The command tree node with DBOP_prior_command_tree can have
        //  siblings, but it must not have any children.
        //  Likewise, the original command tree can have children, but it
        //  must not have any siblings.
        //  Splice the trees together by copying the root node of the
        //  original tree onto the DBOP_prior_command_tree node, then
        //  freeing the orginal root node.
        //
        if (pNodes[0]->pctFirstChild != 0 ||
            pNodes[0]->wKind != DBVALUEKIND_EMPTY)
            THROW(CException(E_INVALIDARG));    // DB_E_BADCOMMANDTREE???

        // Perhaps we should just substitute a DBOP_table_identifier
        // node with default table in this case.

        if (0 == _pQueryTree)
            THROW(CException(E_INVALIDARG));    // DB_E_NOCOMMANDTREE???

        //
        //  Transfer the pointers and values from the root node
        //  to the prior_command_tree node.
        //

        _pQueryTree->TransferNode( CDbCmdTreeNode::CastFromStruct(pNodes[0]) );
        Win4Assert(0 == _pQueryTree->GetFirstChild() &&
                   0 == _pQueryTree->GetNextSibling() &&
                   DBVALUEKIND_EMPTY == _pQueryTree->GetValueType());

        delete _pQueryTree;
        _pQueryTree = TreeCopy.Acquire();
    }
    CATCH( CException, e )
    {
        sc = _DBErrorObj.PostHResult( e, IID_IQuery );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetCardinalityEstimate, public
//
//  Synopsis:   Get estimated cardinality of the query tree
//
//  Arguments:  [pulCardinality] -- Pointer to memory to hold cardinality
//
//  History:    29 Jun 95   AlanW       Created
//              2 May  97   KrishnaN    Added this header block
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::GetCardinalityEstimate(
    DBORDINAL *       pulCardinality)
{
    _DBErrorObj.ClearErrorInfo();
    vqDebugOut(( DEB_WARN, "CRootQuerySpec::GetCardinalityEstimate not implemented\n" ));
    return _DBErrorObj.PostHResult(S_FALSE, IID_IQuery);
}

//
//  ICommandProperties methods
//

//+---------------------------------------------------------------------------
//
//  Method:     DetermineScodeIndex
//
//  Synopsis:   Returns an index into a static array of SCODEs
//
//              NOTE: This function will go away once CRowsetProperties and
//                    CMRowsetProps are merged completely.
//
//  Arguments:  [sc]    - SCODE for which an index is returned
//
//  History:     01-05-98       danleg      Created
//
//----------------------------------------------------------------------------

inline ULONG DetermineScodeIndex
    (
    SCODE sc
    )
{
    switch( sc )
    {
        case S_OK:                  return 0;
        case DB_S_ERRORSOCCURRED:   return 1;
        case E_FAIL:
        default:                    return 2;
        case E_INVALIDARG:          return 3;
        case E_OUTOFMEMORY:         return 4;
        case DB_E_ERRORSOCCURRED:   return 5;
    }
}

//+---------------------------------------------------------------------------
//
//  Method:     DetermineNewSCODE
//
//  Synopsis:   Given two SCODEs returned by the two property handling
//              mechanisms used, returned a resultant SCODE to return from
//              Get/SetProperties.
//
//              NOTE: This function will go away once CRowsetProperties and
//                    CMRowsetProps are merged completely.
//
//  Arguments:
//
//  History:     01-05-98       danleg      Created
//
//----------------------------------------------------------------------------

SCODE DetermineNewSCODE
    (
    SCODE   sc1,
    SCODE   sc2
    )
{
    ULONG   isc1 = DetermineScodeIndex(sc1),
            isc2 = DetermineScodeIndex(sc2);

static const SCODE  s_rgPropHresultMap[6][6] =
    {
        {S_OK,                  DB_S_ERRORSOCCURRED,    E_FAIL, E_INVALIDARG,   E_OUTOFMEMORY,  DB_S_ERRORSOCCURRED},
        {DB_S_ERRORSOCCURRED,   DB_S_ERRORSOCCURRED,    E_FAIL, E_INVALIDARG,   E_OUTOFMEMORY,  DB_S_ERRORSOCCURRED},
        {E_FAIL,                E_FAIL,                 E_FAIL, E_FAIL,         E_FAIL,         E_FAIL},
        {E_INVALIDARG,          E_INVALIDARG,           E_FAIL, E_INVALIDARG,   E_INVALIDARG,   E_INVALIDARG},
        {E_OUTOFMEMORY,         E_OUTOFMEMORY,          E_FAIL, E_OUTOFMEMORY,  E_OUTOFMEMORY,  E_OUTOFMEMORY},
        {DB_S_ERRORSOCCURRED,   DB_S_ERRORSOCCURRED,    E_FAIL, E_INVALIDARG,   E_OUTOFMEMORY,  DB_E_ERRORSOCCURRED},
    };

    return s_rgPropHresultMap[isc1][isc2];
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetProperties, public
//
//  Synopsis:   Get rowset properties
//
//  Arguments:  [cPropertySetIDs]    - number of desired properties or 0
//              [rgPropertySetIDs]   - array of desired properties or NULL
//              [pcPropertySets]     - number of property sets returned
//              [prgPropertySets]    - array of returned property sets
//
//  History:     16 Nov 95   AlanW       Created
//               02-22-98    danleg      Changed to use CMRowsetProps
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::GetProperties(
    const ULONG       cPropertySetIDs,
    const DBPROPIDSET rgPropertySetIDs[],
    ULONG *           pcPropertySets,
    DBPROPSET **      prgPropertySets)
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        _RowsetProps.GetPropertiesArgChk( cPropertySetIDs,
                                          rgPropertySetIDs,
                                          pcPropertySets,
                                          prgPropertySets );

        sc = _RowsetProps.GetProperties( cPropertySetIDs,
                                         rgPropertySetIDs,
                                         pcPropertySets,
                                         prgPropertySets );
    }
    CATCH( CException, e )
    {
        //
        // Don't PostHResult here.  Let the caller do the posting.
        //
        sc = _DBErrorObj.PostHResult( e, IID_ICommandProperties );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
} //GetProperties

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::SetProperties, public
//
//  Synopsis:   Set rowset properties
//
//  Arguments:  [cPropertySets]  - number of property sets
//              [rgProperties]   - array of property sets to be set
//
//  History:     16 Nov 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::SetProperties(
    ULONG                  cPropertySets,
    DBPROPSET              rgPropertySets[])
{
    _DBErrorObj.ClearErrorInfo();

    if ( HaveQuery() && _pInternalQuery->IsQueryActive() )
        return _DBErrorObj.PostHResult(DB_E_OBJECTOPEN, IID_ICommandProperties);

    //
    // Quick return
    //
    if( cPropertySets == 0 )
        return S_OK;

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {

        CUtlProps::SetPropertiesArgChk( cPropertySets, rgPropertySets );

        if ( IsRowsetOpen() )
            THROW( CException(DB_E_OBJECTOPEN) );

        DWORD dwPropFlags = _RowsetProps.GetPropertyFlags();

        sc = _RowsetProps.SetProperties( cPropertySets, rgPropertySets );

        if ( SUCCEEDED( sc ) )
        {
            if ( _pColumnsInfo &&
                 _RowsetProps.GetPropertyFlags() != dwPropFlags )
                InitColumns();
        }
    }
    CATCH( CException, e )
    {
        //
        // Don't PostHResult here.  Let the caller do the posting.
        //

        sc = e.GetErrorCode();
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//
//  IServiceProperties methods
//

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetPropertyInfo, public
//
//  Synopsis:   Get rowset properties
//
//  Arguments:  [cPropertySetIDs]    - number of desired properties or 0
//              [rgPropertySetIDs]   - array of desired properties or NULL
//              [pcPropertySets]     - number of property sets returned
//              [prgPropertySets]    - array of returned property sets
//              [ppwszDesc]          - if non-zero, property descriptions are
//                                     returneed
//
//  History:     16 Nov 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::GetPropertyInfo(
    const ULONG            cPropertySetIDs,
    const DBPROPIDSET      rgPropertySetIDs[],
    ULONG *                pcPropertySets,
    DBPROPINFOSET **       prgPropertySets,
    WCHAR **               ppwszDesc)
{
    if ( (0 != cPropertySetIDs && 0 == rgPropertySetIDs) ||
          0 == pcPropertySets ||
          0 == prgPropertySets )
    {
        if (pcPropertySets)
           *pcPropertySets = 0;
        if (prgPropertySets)
           *prgPropertySets = 0;
        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IServiceProperties);
    }

    SCODE sc = S_OK;
    *pcPropertySets = 0;
    *prgPropertySets = 0;
    if (ppwszDesc)
       *ppwszDesc = 0;


    TRANSLATE_EXCEPTIONS;
    TRY
    {
        sc = _PropInfo.GetPropertyInfo( cPropertySetIDs,
                                        rgPropertySetIDs,
                                        pcPropertySets,
                                        prgPropertySets,
                                        ppwszDesc );

        // Don't PostHResult here -- it's a good chance it's a scope
        // property that we're expecting to fail.  Spare the expense.
        // The child object will post the error for us.
    }
    CATCH( CException, e )
    {
        _DBErrorObj.PostHResult(e.GetErrorCode(), IID_IServiceProperties);
        sc = GetOleError(e);

    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::SetRequestedProperties, public
//
//  Synopsis:   Set rowset properties
//
//  Arguments:  [cPropertySets]  - number of property sets
//              [rgProperties]   - array of property sets to be set
//
//  History:    16 Nov 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::SetRequestedProperties(
    ULONG                  cPropertySets,
    DBPROPSET              rgPropertySets[])
{
    if ( HaveQuery() && _pInternalQuery->IsQueryActive() )
        return _DBErrorObj.PostHResult(DB_E_OBJECTOPEN, IID_IServiceProperties);

    if ( 0 != cPropertySets && 0 == rgPropertySets)
        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IServiceProperties);

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        sc = _RowsetProps.SetProperties( cPropertySets,
                                         rgPropertySets );

        // Don't PostHResult here -- it's a good chance it's a scope
        // property that we're expecting to fail.  Spare the expense.
        // The child object will post the error for us.
    }
    CATCH( CException, e )
    {
        _DBErrorObj.PostHResult(e.GetErrorCode(), IID_IServiceProperties);
        sc = GetOleError(e);
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::SetSuppliedProperties, public
//
//  Synopsis:   Set rowset properties
//
//  Arguments:  [cPropertySets]  - number of property sets
//              [rgProperties]   - array of property sets to be set
//
//  History:     16 Nov 95   AlanW       Created
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::SetSuppliedProperties(
    ULONG                  cPropertySets,
    DBPROPSET              rgPropertySets[])
{
    if ( HaveQuery() && _pInternalQuery->IsQueryActive() )
        return _DBErrorObj.PostHResult(DB_E_OBJECTOPEN, IID_IServiceProperties);

    if ( 0 != cPropertySets && 0 == rgPropertySets)
        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IServiceProperties);

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        sc = _RowsetProps.SetProperties( cPropertySets,
                                         rgPropertySets );

        // Don't PostHResult here -- it's a good chance it's a scope
        // property that we're expecting to fail.  Spare the expense.
        // The child object will post the error for us.
    }
    CATCH( CException, e )
    {
        _DBErrorObj.PostHResult(e.GetErrorCode(), IID_IServiceProperties);
        sc = GetOleError(e);
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::_CheckRootNode, private
//
//  Synopsis:   Check a client's root node for validity
//
//  Arguments:  [pRoot] -- DBCOMMANDTREE node at root of tree
//
//  Notes:      A command tree root node may have children, but it
//              may not have siblings.
//
//  History:    29 Jun 95   AlanW       Created
//
//----------------------------------------------------------------------------

void CRootQuerySpec::_CheckRootNode(
    const DBCOMMANDTREE *  pRoot)
{
    if (pRoot->pctNextSibling)
        THROW(CException(E_INVALIDARG)); 
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::GetColumnsInfo, private
//
//  Synopsis:   Create an IColumnsInfo* for the columns in the query tree
//
//  Arguments:  -none-
//
//  Returns:    CColumnsInfo* - a pointer to a CColumnsInfo that implements
//                              IColumnsInfo.
//
//----------------------------------------------------------------------------

static GUID guidBmk = DBBMKGUID;

CColumnsInfo * CRootQuerySpec::GetColumnsInfo()
{
    if ( 0 == _pColumnsInfo )
    {
        _pColumnsInfo = new CColumnsInfo( *((IUnknown *) (ICommand *) this),
                                          _DBErrorObj, FALSE );

        InitColumns( );
    }

    return _pColumnsInfo;
}


//+---------------------------------------------------------------------------
//
//  Method:     CRootQuerySpec::InitColumns, private
//
//  Synopsis:   Reinitialize the columns associated with the CColumnsInfo
//
//  Arguments:  -none-
//
//----------------------------------------------------------------------------

void CRootQuerySpec::InitColumns( )
{
    if ( _pQueryTree && !(_dwStatus & CMD_COLINFO_NOTPREPARED) )
    {
        BOOL fSequential = TRUE;        // need to know for CColumnsInfo ctor
        //
        // Fault-in columnsinfo.
        //
        CParseCommandTree   Parse;
        Parse.ParseTree( _pQueryTree );

        CCategorizationSet & rCateg = Parse.GetCategorization();
        CPidMapperWithNames & pidmap = Parse.GetPidmap();
        CColumnSet const * pColSet = &Parse.GetOutputColumns();

        if ( 0 != rCateg.Count() )
        {
            //
            // Ole-db spec says that for categorization, we must use the
            // top-level columns only.
            //
            pColSet = &(rCateg.Get(0)->GetColumnSet());
        }

        for ( unsigned i = 0; i < pColSet->Count(); i++ )
        {
            CFullPropSpec const * pPropSpec = pidmap.PidToName( pColSet->Get(i));
            if (pPropSpec->IsPropertyPropid() &&
                pPropSpec->GetPropertyPropid() == PROPID_DBBMK_BOOKMARK &&
                pPropSpec->GetPropSet() == guidBmk)
               fSequential = FALSE;
        }

        if ( _RowsetProps.GetPropertyFlags() &
             ( eLocatable | eScrollable ) )
            fSequential = FALSE;

        _pColumnsInfo->InitColumns( *pColSet,
                                    pidmap,
                                    fSequential );
    }
    else
    {
        _pColumnsInfo->InitColumns( (_dwStatus & CMD_COLINFO_NOTPREPARED) );
    }
}


//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::CreateAccessor
//
//  Synopsis:   Makes an accessor that a client can use to get data.
//
//  Arguments:  [dwAccessorFlags]  -- read/write access requested
//              [cBindings]    -- # of bindings in rgBindings
//              [rgBindings]   -- array of bindings for the accessor to support
//              [cbRowSize]    -- ignored for IRowset
//              [phAccessor]   -- returns created accessor if all is ok
//              [rgBindStatus] -- array of binding statuses
//
//  Returns:    SCODE error code
//
//  History:    11-07-95   srikants   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::CreateAccessor(
    DBACCESSORFLAGS     dwAccessorFlags,
    DBCOUNTITEM         cBindings,
    const DBBINDING     rgBindings[],
    DBLENGTH            cbRowSize,
    HACCESSOR *         phAccessor,
    DBBINDSTATUS        rgBindStatus[])
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    if (0 == phAccessor || (0 != cBindings && 0 == rgBindings))
        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IAccessor);

    // Make sure pointer is good while zeroing in case of a later error

    *phAccessor = 0;


    TRANSLATE_EXCEPTIONS;
    TRY
    {
        CColumnsInfo * pColumnsInfo = 0;

        XPtr<CRowDataAccessor> Accessor(new CRowDataAccessor(dwAccessorFlags,
                                                             cBindings,
                                                             rgBindings,
                                                             rgBindStatus,
                                   (_RowsetProps.GetPropertyFlags() & eExtendedTypes) != 0,
                                                             (IUnknown *) (ICommand *) this,
                                                             pColumnsInfo
                                                             ));
        CLock lck( _mtxCmd );

        _aAccessors.Add( Accessor.GetPointer() );

        *phAccessor = (Accessor.Acquire())->Cast();
    }
    CATCH(CException, e)
    {
        sc = _DBErrorObj.PostHResult( e, IID_IAccessor );
        _DBErrorObj.PostHResult( sc, IID_IAccessor );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::GetBindings, private
//
//  Synopsis:   Returns an accessor's bindings
//
//  Arguments:  [hAccessor]   -- accessor being queried
//              [dwAccessorFlags] -- returns read/write access of accessor
//              [pcBindings]  -- returns # of bindings in rgBindings
//              [prgBindings] -- returns array of bindings
//
//  Returns:    SCODE error code
//
//  History:    14 Dec 94       dlee   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::GetBindings(
    HACCESSOR         hAccessor,
    DBACCESSORFLAGS * pdwAccessorFlags,
    DBCOUNTITEM *     pcBindings,
    DBBINDING * *     prgBindings) /*const*/
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    if (0 == pdwAccessorFlags ||
        0 == pcBindings ||
        0 == prgBindings)
    {
        // fill in error values where possible
        if (pdwAccessorFlags)
           *pdwAccessorFlags = DBACCESSOR_INVALID;
        if (pcBindings)
           *pcBindings = 0;
        if (prgBindings)
           *prgBindings = 0;

        return _DBErrorObj.PostHResult(E_INVALIDARG, IID_IAccessor);
    }

    *pdwAccessorFlags = DBACCESSOR_INVALID;
    *pcBindings = 0;
    *prgBindings = 0;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        CLock lck( _mtxCmd );
        CAccessor * pAccessor = (CAccessor *)_aAccessors.Convert( hAccessor );
        pAccessor->GetBindings(pdwAccessorFlags, pcBindings, prgBindings);
    }
    CATCH(CException, e)
    {
        sc = _DBErrorObj.PostHResult( e, IID_IAccessor );
        _DBErrorObj.PostHResult( sc, IID_IAccessor );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;


    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::AddRefAccessor, private
//
//  Synopsis:   Frees an accessor
//
//  Arguments:  [hAccessor]   -- accessor being freed
//              [pcRefCount]  -- pointer to residual refcount (optional)
//
//  Returns:    SCODE error code
//
//  History:    14 Dec 94       dlee   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::AddRefAccessor(
    HACCESSOR   hAccessor,
    ULONG *     pcRefCount )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        CLock lck( _mtxCmd );
        _aAccessors.AddRef( hAccessor, pcRefCount );
    }
    CATCH(CException, e)
    {
        sc = _DBErrorObj.PostHResult( e, IID_IAccessor );
        _DBErrorObj.PostHResult(sc, IID_IAccessor);
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::ReleaseAccessor, private
//
//  Synopsis:   Frees an accessor
//
//  Arguments:  [hAccessor]   -- accessor being freed
//              [pcRefCount]  -- pointer to residual refcount (optional)
//
//  Returns:    SCODE error code
//
//  History:    14 Dec 94       dlee   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::ReleaseAccessor(
    HACCESSOR   hAccessor,
    ULONG *     pcRefCount )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        CLock lck( _mtxCmd );
        _aAccessors.Release( hAccessor, pcRefCount );
    }
    CATCH(CException, e)
    {
        sc = _DBErrorObj.PostHResult( e, IID_IAccessor );
        _DBErrorObj.PostHResult(sc, IID_IAccessor);
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::CanConvert, public
//
//  Synopsis:   Indicate whether a type conversion is valid.
//
//  Arguments:  [wFromType]      -- source type
//              [wToType]        -- destination type
//              [dwConvertFlags] -- read/write access requested
//
//  Returns:    S_OK if the conversion is available, S_FALSE otherwise.
//              E_FAIL, E_INVALIDARG or DB_E_BADCONVERTFLAG on errors.
//
//  History:    20 Nov 96      AlanW    Created
//              14 Jan 98      VikasMan Passed IDataConvert(OLE-DB data conv.
//                                      interface) to CanConvertType
//
//----------------------------------------------------------------------------

STDMETHODIMP CRootQuerySpec::CanConvert( DBTYPE wFromType, DBTYPE wToType, DBCONVERTFLAGS dwConvertFlags )
{
    _DBErrorObj.ClearErrorInfo();

    SCODE sc = S_OK;

    TRANSLATE_EXCEPTIONS;
    TRY
    {
        if (((dwConvertFlags & DBCONVERTFLAGS_COLUMN) &&
             (dwConvertFlags & DBCONVERTFLAGS_PARAMETER)) ||
            (dwConvertFlags & ~(DBCONVERTFLAGS_COLUMN |
                                DBCONVERTFLAGS_PARAMETER |
                                DBCONVERTFLAGS_ISFIXEDLENGTH |
                                DBCONVERTFLAGS_ISLONG |
                                DBCONVERTFLAGS_FROMVARIANT)))
        {
            sc = DB_E_BADCONVERTFLAG;
        }
        else if ( ( dwConvertFlags & DBCONVERTFLAGS_FROMVARIANT ) &&
                  !IsValidFromVariantType(wFromType) )
        {
            sc = DB_E_BADTYPE;
        }
        else
        {
            // Allocate this on the stack
            XInterface<IDataConvert> xDataConvert;

            BOOL fOk = CAccessor::CanConvertType( wFromType,
                                                  wToType,
                                   (_RowsetProps.GetPropertyFlags() & eExtendedTypes) != 0,
                                    xDataConvert );
            sc = fOk ? S_OK : S_FALSE;
        }
        if (FAILED(sc))
            _DBErrorObj.PostHResult(sc, IID_IConvertType);
    }
    CATCH(CException, e)
    {
        sc = _DBErrorObj.PostHResult( e, IID_IConvertType );
        _DBErrorObj.PostHResult( sc, IID_IConvertType );
    }
    END_CATCH;
    UNTRANSLATE_EXCEPTIONS;

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::BuildTree, public
//
//  Synopsis:   Takes a cached SQL Text and translates it into a Query Tree
//              Called from Execute(), Prepare() and GetCommandTree().
//
//  Arguments:  [pIID]      -  IID of interface calling this function
//
//
//  Returns:    [S_OK]          - a command tree was built successfully
//              [DB_S_NORESULT] - the command didn't generate a tree
//                                (e.g SET PROPERTYNAME...)
//              All error codes are thrown.
//
//
//  History:    10-31-97        danleg      Created
//              01-01-98        danleg      Moved global views to CreateParser
//
//----------------------------------------------------------------------------

SCODE CRootQuerySpec::BuildTree( )
{
    SCODE sc = S_OK;

    DBCOMMANDTREE * pDBCOMMANDTREE = 0;

    XInterface<IParserTreeProperties> xIPTProperties;

    LCID lcidContent =   GetLCIDFromString((LPWSTR)_RowsetProps.GetValString(
                                    CMRowsetProps::eid_DBPROPSET_MSIDXS_ROWSET_EXT,
                                    CMRowsetProps::eid_MSIDXSPROPVAL_COMMAND_LOCALE_STRING));

    //
    // Synch IParserSession's catalog cache with DBPROP_CURRENTCATALOG.  If no session,
    // catalogs are specified using the catalog..scope() syntax anyway.
    //
    if ( !_xSession.IsNull() )
    {
        LPCWSTR pwszCatalog = ((_xSession->GetDataSrcPtr())->GetDSPropsPtr())->GetValString(
                                    CMDSProps::eid_DBPROPSET_DATASOURCE,
                                    CMDSProps::eid_DBPROPVAL_CURRENTCATALOG );

        _xpIPSession->SetCatalog( pwszCatalog );
    }

    // NOTE: For CREATE VIEW and SET PROPERTYNAME calls, ToTree will return DB_S_NORESULT
    sc = _xpIPSession->ToTree( lcidContent,
                               _pwszSQLText,
                               &pDBCOMMANDTREE,
                               xIPTProperties.GetPPointer() );

    if( S_OK == sc )
    {
        //
        // Set flag to indicate that BuildTree is calling SetCommandTree
        //
        _dwStatus |= CMD_TEXT_TOTREE;

        // Set the command tree
        sc = SetCommandTree( &pDBCOMMANDTREE, DBCOMMANDREUSE_NONE, FALSE );
        if ( SUCCEEDED(sc) )
        {
            VARIANT vVal;
            VariantInit( &vVal );

            // We need to set the QUERY_RESTRICTION so it can be cloned into
            // the rowset properties object
            if( SUCCEEDED( xIPTProperties->GetProperties(PTPROPS_CIRESTRICTION,
                                                         &vVal)) )
            {
                _RowsetProps.SetValString(
                    CMRowsetProps::eid_DBPROPSET_MSIDXS_ROWSET_EXT,
                    CMRowsetProps::eid_MSIDXSPROPVAL_QUERY_RESTRICTION,
                    (LPCWSTR)V_BSTR(&vVal) );
            }
            VariantClear( &vVal );
        }
    }
    else if( FAILED(sc) && !xIPTProperties.IsNull() )
    {
        // Retrieve Error Information
        SCODE           sc2 = S_OK;
        DISPPARAMS *    pDispParams = NULL;
        VARIANT         vVal[3];
        ULONG           ul;

        for(ul=0; ul<NUMELEM(vVal); ul++)
            VariantInit(&vVal[ul]);

        if( SUCCEEDED(sc2 = xIPTProperties->GetProperties(
                                            PTPROPS_ERR_IDS, &vVal[0])) )
        {
            if( (V_I4(&vVal[0]) > 0) && SUCCEEDED(sc2 = xIPTProperties->GetProperties(
                                                PTPROPS_ERR_HR, &vVal[1])) )
            {
                if( SUCCEEDED(sc2 = xIPTProperties->GetProperties(
                                                    PTPROPS_ERR_DISPPARAM, &vVal[2])) )
                {
                    // Change safearray into DISPPARMS
                    SAFEARRAY* psa = V_ARRAY(&vVal[2]);
                    if( psa && psa->rgsabound[0].cElements)
                    {
                        pDispParams = (DISPPARAMS*)CoTaskMemAlloc(sizeof(DISPPARAMS));
                        if( pDispParams )
                        {
                            pDispParams->cNamedArgs = 0;
                            pDispParams->rgdispidNamedArgs = NULL;
                            pDispParams->cArgs = psa->rgsabound[0].cElements;
                            pDispParams->rgvarg =
                                (VARIANT*)CoTaskMemAlloc(sizeof(VARIANTARG) * psa->rgsabound[0].cElements);
                            if( pDispParams->rgvarg )
                            {
                                for (ULONG i=0; i<psa->rgsabound[0].cElements; i++)
                                {
                                    VariantInit(&(pDispParams->rgvarg[i]));
                                    V_VT(&(pDispParams->rgvarg[i])) = VT_BSTR;
                                    V_BSTR(&(pDispParams->rgvarg[i])) = ((BSTR*)psa->pvData)[i];
                                    ((BSTR*)psa->pvData)[i] = NULL;
                                }
                            }
                        }
                    }

                    // Post a parser error
                    _DBErrorObj.PostParserError( V_I4(&vVal[1]),
                                                 V_I4(&vVal[0]),
                                                 &pDispParams );

                    // This is the SCODE of the error just posted.
                    sc2 = V_I4(&vVal[1]);
                    if ( FAILED(sc2) )
                        sc = sc2;

                    Win4Assert( pDispParams == NULL ); // Should be null after post
                }
            }
        }

        for ( ul=0; ul<NUMELEM(vVal); ul++ )
            VariantClear( &vVal[ul] );
    }

    if( pDBCOMMANDTREE )
        FreeCommandTree( &pDBCOMMANDTREE );

    if ( FAILED(sc) )
        THROW( CException(sc) );

    return sc;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRootQuerySpec::CreateParser, private
//
//  Synopsis:   Creates a parser object if one was not passed through a session
//
//  History:    11-25-97    danleg      Created
//
//----------------------------------------------------------------------------

void CRootQuerySpec::CreateParser()
{
    SCODE sc = S_OK;

    CLock lck( _mtxCmd );

    sc = MakeIParser( _xIParser.GetPPointer() );
    if ( FAILED(sc) )
    {
        Win4Assert( sc != E_INVALIDARG );
        THROW( CException(sc) );
    }

    XInterface<IColumnMapperCreator> xColMapCreator;

    XInterface<CImpIParserVerify> xIPVerify(new CImpIParserVerify());

    //
    // Create an IParserSession object
    //
    xIPVerify->GetColMapCreator( ((IColumnMapperCreator**)xColMapCreator.GetQIPointer()) );

    sc = _xIParser->CreateSession( &DBGUID_MSSQLTEXT,
                                   DEFAULT_MACHINE,
                                   xIPVerify.GetPointer(),
                                   xColMapCreator.GetPointer(),
                                   (IParserSession**)_xpIPSession.GetQIPointer() );
    if ( FAILED(sc) )
        THROW( CException(sc) );

    //
    // Set default catalog in the parser session object
    //
    _xpwszCatalog.Init( MAX_PATH );
    ULONG cOut;
    sc = xIPVerify->GetDefaultCatalog( _xpwszCatalog.GetPointer(),
                                       _xpwszCatalog.Count(),
                                       &cOut);

    //
    // _xpwszCatalog isn't long enough
    //
    Win4Assert( E_INVALIDARG != sc );
    if ( FAILED(sc) )
        THROW( CException(sc) );

    sc = _xpIPSession->SetCatalog( _xpwszCatalog.GetPointer() );
    if( FAILED(sc) )
        THROW( CException(sc) );


    DBCOMMANDTREE * pDBCOMMANDTREE = 0;
    XInterface<IParserTreeProperties> xIPTProperties;

    LCID lcidContent =   GetLCIDFromString((LPWSTR)_RowsetProps.GetValString(
                                    CMRowsetProps::eid_DBPROPSET_MSIDXS_ROWSET_EXT,
                                    CMRowsetProps::eid_MSIDXSPROPVAL_COMMAND_LOCALE_STRING));

    //
    // Predefined views
    //
    sc = _xpIPSession->ToTree( lcidContent,
                               s_pwszPredefinedViews,
                               &pDBCOMMANDTREE,
                               xIPTProperties.GetPPointer() );
    if ( FAILED(sc) )
        THROW( CException(sc) );
} //CreateParser