//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1996 - 2000.
//
//  File:       ixsquery.cxx
//
//  Contents:   Query SSO active query state class
//
//  History:    29 Oct 1996      Alanw    Created
//
//----------------------------------------------------------------------------

#include "pch.cxx"
#pragma hdrstop

#include "ixsso.hxx"
#include "ssodebug.hxx"

#include <strrest.hxx>
#include <strsort.hxx>
#include <qlibutil.hxx>

#if CIDBG
#include <stdio.h>
#endif // CIDBG

#include <initguid.h>
#include <nlimport.h>

static const DBID dbcolNull = { {0,0,0,{0,0,0,0,0,0,0,0}},DBKIND_GUID_PROPID,0};

static const GUID guidQueryExt = DBPROPSET_QUERYEXT;

static const GUID guidRowsetProps = DBPROPSET_ROWSET;


//+---------------------------------------------------------------------------
//
//  Member:     CixssoQuery::GetDefaultCatalog - private inline
//
//  Synopsis:   Initializes the _pwszCatalog member with the default catalog.
//
//  Arguments:  NONE
//
//  Notes:      The IS 2.0 implementation of ISCC::GetDefaultCatalog has two
//              flaws that are worked around here.  It should return the size
//              of the required string when zero is passed in as the input
//              length, and it should null terminate the output string.
//
//  History:    18 Dec 1996     AlanW   Created
//              21 Oct 1997     AlanW   Modified to use ISimpleCommandCreator
//
//----------------------------------------------------------------------------

inline void CixssoQuery::GetDefaultCatalog( )
{
    ixssoDebugOut(( DEB_ITRACE, "Using default catalog\n" ));

    ULONG cchRequired = 0;

    SCODE sc = _xCmdCreator->GetDefaultCatalog(0, 0, &cchRequired);

    if ( cchRequired == 0 )
    {
        // IS 2.0 doesn't return required path length
        cchRequired = MAX_PATH;
    }
    else if ( cchRequired > MAX_PATH )
    {
        THROW( CException(E_INVALIDARG) );
    }
    cchRequired++;                // make room for termination

    XArray<WCHAR> pwszCat ( cchRequired );
    sc = _xCmdCreator->GetDefaultCatalog(pwszCat.GetPointer(), cchRequired, &cchRequired);

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

    Win4Assert( 0 == _pwszCatalog );
    _pwszCatalog = pwszCat.Acquire();

    // IS 2.0 does not transfer the null character at the end of the string.
    _pwszCatalog[ cchRequired ] = L'\0';

    return;
}


//+---------------------------------------------------------------------------
//
//  Function:   ParseCatalogs - public
//
//  Synopsis:   Parse a comma-separated catalog string, return the count
//              of catalogs and the individual catalog and machine names.
//
//  Arguments:  [pwszCatalog] - input catalog string
//              [aCatalogs] - array for returned catalog names
//              [aMachines] - array for returned machine names
//
//  Returns:    ULONG - number of catalogs
//
//  Notes:      
//
//  History:    18 Jun 1997      Alanw    Created
//
//----------------------------------------------------------------------------

ULONG ParseCatalogs( WCHAR * pwszCatalog,
                     CDynArray<WCHAR> & aCatalogs,
                     CDynArray<WCHAR> & aMachines )
{
    ULONG cCatalogs = 0;

    while ( 0 != *pwszCatalog )
    {
        // eat space and commas

        while ( L' ' == *pwszCatalog || L',' == *pwszCatalog )
            pwszCatalog++;

        if ( 0 == *pwszCatalog )
            break;

        WCHAR awchCat[MAX_PATH];
        WCHAR * pwszOut = awchCat;

        // is this a quoted path?

        if ( L'"' == *pwszCatalog )
        {
            pwszCatalog++;

            while ( 0 != *pwszCatalog &&
                    L'"' != *pwszCatalog &&
                    pwszOut < &awchCat[MAX_PATH] )
                *pwszOut++ = *pwszCatalog++;

            if ( L'"' != *pwszCatalog )
                THROW( CIxssoException( MSG_IXSSO_BAD_CATALOG, 0 ) );

            pwszCatalog++;
            *pwszOut++ = 0;
        }
        else
        {
            while ( 0 != *pwszCatalog &&
                    L',' != *pwszCatalog &&
                    pwszOut < &awchCat[MAX_PATH] )
                *pwszOut++ = *pwszCatalog++;

            if ( pwszOut >= &awchCat[MAX_PATH] )
                THROW( CIxssoException( MSG_IXSSO_BAD_CATALOG, 0 ) );

            // back up over trailing spaces

            while ( L' ' == * (pwszOut - 1) )
                pwszOut--;

            *pwszOut++ = 0;
        }

        XPtrST<WCHAR> xpCat;
        XPtrST<WCHAR> xpMach;
        SCODE sc = ParseCatalogURL( awchCat, xpCat, xpMach );
        if (FAILED(sc) || xpCat.IsNull() )
            THROW( CIxssoException( MSG_IXSSO_BAD_CATALOG, 0 ) );

        aCatalogs.Add(xpCat.GetPointer(), cCatalogs);
        xpCat.Acquire();
        aMachines.Add(xpMach.GetPointer(), cCatalogs);
        xpMach.Acquire();

        cCatalogs++;
    }

    if ( 0 == cCatalogs )
        THROW( CIxssoException( MSG_IXSSO_BAD_CATALOG, 0 ) );

    return cCatalogs;
} //ParseCatalogs


//+---------------------------------------------------------------------------
//
//  Member:     CixssoQuery::GetDialect - private
//
//  Synopsis:   Parses the dialect string and returns ISQLANG_V*
//
//  Returns:    The dialect identifier
//
//  History:    19 Nov 1997      dlee       Created
//              03 Dec 1998      KrishnaN   Defaulting to version 2
//
//----------------------------------------------------------------------------
ULONG CixssoQuery::GetDialect()
{
    if ( 0 == _pwszDialect )
        return ISQLANG_V2;

    ULONG ul = _wtoi( _pwszDialect );
    if ( ul < ISQLANG_V1 || ul > ISQLANG_V2 )
        return ISQLANG_V2;

    return ul;
} //GetDialect

//+---------------------------------------------------------------------------
//
//  Member:     CixssoQuery::ExecuteQuery - private
//
//  Synopsis:   Executes the query and builds an IRowset or IRowsetScroll
//              as necessary.
//
//  Arguments:  NONE
//
//  History:    29 Oct 1996      Alanw    Created
//
//----------------------------------------------------------------------------

void CixssoQuery::ExecuteQuery( )
{
    Win4Assert( 0 == _pIRowset );     // Should not have executed query

    //
    //  Setup the variables needed to execute this query; including:
    //
    //      Query
    //      MaxRecords
    //      SortBy
    //

    ixssoDebugOut(( DEB_TRACE, "ExecuteQuery:\n" ));
    ixssoDebugOut(( DEB_TRACE, "\tQuery = '%ws'\n", _pwszRestriction ));
    if ( 0 == _pwszRestriction || 0 == *_pwszRestriction )
    {
        THROW( CIxssoException(MSG_IXSSO_MISSING_RESTRICTION, 0) );
    }


    ixssoDebugOut(( DEB_TRACE, "\tMaxRecords = %d\n", _maxResults ));
    ixssoDebugOut(( DEB_TRACE, "\tFirstRowss = %d\n", _cFirstRows ));

    //
    //  Get the columns in the query
    //
    ixssoDebugOut(( DEB_TRACE, "\tColumns = '%ws'\n", _pwszColumns ));
    if ( 0 == _pwszColumns || 0 == *_pwszColumns )
    {
        THROW( CIxssoException(MSG_IXSSO_MISSING_OUTPUTCOLUMNS, 0) );
    }

    if ( 0 != _pwszGroup && 0 != *_pwszGroup && _fSequential )
    {
        // Grouped queries are always non-sequential.
        _fSequential = FALSE;
    }

    //
    // Convert the textual form of the restriction, output columns and
    // sort columns into a DBCOMMANDTREE.
    //
    if (InvalidLCID == _lcid)
    {
        THROW( CIxssoException(MSG_IXSSO_INVALID_LOCALE, 0) );
    }

    ULONG ulDialect = GetDialect();

    CTextToTree textToTree( _pwszRestriction,
                            ulDialect,
                            _pwszColumns,
                            GetColumnMapper(),
                            _lcid,
                            _pwszSort,
                            _pwszGroup,
                            0,
                            _maxResults,
                            _cFirstRows,
                            TRUE            // Keep the friendly column names
                          );


    CDbCmdTreeNode * pDbCmdTree =  (CDbCmdTreeNode *) (void *) textToTree.FormFullTree();
    XPtr<CDbCmdTreeNode> xDbCmdTree( pDbCmdTree );

    CDynArray<WCHAR>    apCatalog;
    CDynArray<WCHAR>    apMachine;

    //
    //  Get the location of the catalog.  Use the default if the catalog
    //  property is not set.
    //
    if ( 0 == _pwszCatalog )
    {
        GetDefaultCatalog();
        if ( 0 == _pwszCatalog )
            THROW( CIxssoException(MSG_IXSSO_NO_SUCH_CATALOG, 0) );
    }
    
    ixssoDebugOut(( DEB_TRACE, "\tCatalog = '%ws'\n", _pwszCatalog ));
    ULONG cCatalogs = ParseCatalogs( _pwszCatalog, apCatalog, apMachine );

    //
    //  Get the scope specification(s) for the query
    //    CiScope
    //    CiFlags
    //

    Win4Assert( _cScopes <= _apwszScope.Size() );

    for ( unsigned i = 0; i < _cScopes; i++)
    {
#   if CIDBG
        char szIdx[10] = "";
        if (_cScopes > 1)
            sprintf( szIdx, " [%d]", i );
#   endif // CIDBG

        ixssoDebugOut(( DEB_TRACE, "\tCiScope%s = '%ws'\n", szIdx, _apwszScope[i] ));

        //
        //  Get the query flags.
        //

        if (i >= _aulDepth.Count())
            _aulDepth[i] = QUERY_DEEP;
        if ( IsAVirtualPath( _apwszScope[i] ) )
            _aulDepth[i] |= QUERY_VIRTUAL_PATH;

        ixssoDebugOut(( DEB_TRACE, "\tCiFlags%s = '%ws'\n", szIdx,
                                      _aulDepth.Get(i) & QUERY_DEEP ? L"DEEP":L"SHALLOW" ));
    }

    //
    //  We've setup all the parameters to run the query.  Run the query
    //  now.
    //
    IUnknown * pIUnknown;
    ICommand *pCommand = 0;

    SCODE sc = _xCmdCreator->CreateICommand(&pIUnknown, 0);

    if (SUCCEEDED (sc)) 
    {
       XInterface<IUnknown> xUnk( pIUnknown );
       sc = pIUnknown->QueryInterface(IID_ICommand, (void **)&pCommand);
    }

    if ( 0 == pCommand )
    {
        THROW( CIxssoException(sc, 0) );
    }

    XInterface<ICommand> xICommand(pCommand);

    if (0 == _cScopes)
    {
        //
        // Default path: search everywhere
        //

        const WCHAR * pwszPath = L"\\";

        DWORD dwDepth = QUERY_DEEP;

        if ( 1 == cCatalogs )
        {
            SetScopeProperties( pCommand,
                                1,
                                &pwszPath,
                                &dwDepth,
                                apCatalog.GetPointer(),
                                apMachine.GetPointer() );
        }
        else
        {
            SetScopeProperties( pCommand,
                                1,
                                &pwszPath,
                                &dwDepth,
                                0,
                                0 );

            SetScopeProperties( pCommand,
                                cCatalogs,
                                0,
                                0,
                                apCatalog.GetPointer(),
                                apMachine.GetPointer() );
        }
    }
    else
    {
        SetScopeProperties( pCommand,
                            _cScopes,
                            (WCHAR const * const *)_apwszScope.GetPointer(),
                            _aulDepth.GetPointer(),
                            0,
                            0 );

        SetScopeProperties( pCommand,
                            cCatalogs,
                            0,
                            0,
                            apCatalog.GetPointer(),
                            apMachine.GetPointer() );
    }

    ICommandTree * pICmdTree = 0;
    sc = xICommand->QueryInterface(IID_ICommandTree, (void **)&pICmdTree);
    if (FAILED (sc) )
    {
        THROW( CException( QUERY_EXECUTE_FAILED ) );
    }

    DBCOMMANDTREE * pDbCommandTree = pDbCmdTree->CastToStruct();
    sc = pICmdTree->SetCommandTree(&pDbCommandTree, DBCOMMANDREUSE_NONE, FALSE);
    pICmdTree->Release();

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

    xDbCmdTree.Acquire();

    //
    //  Set properties on the command object.
    //
    const unsigned MAX_PROPS = 5;
    DBPROPSET  aPropSet[MAX_PROPS];
    DBPROP     aProp[MAX_PROPS];
    ULONG      cProp = 0;
    ULONG      iHitCountProp = MAX_PROPS;

    // Set the property that says whether we want to enumerate

    Win4Assert( cProp < MAX_PROPS );
    ixssoDebugOut(( DEB_TRACE, "\tUseContentIndex = %s\n",
                       _fAllowEnumeration ? "FALSE" : "TRUE" ));

    aProp[cProp].dwPropertyID = DBPROP_USECONTENTINDEX;
    aProp[cProp].dwOptions    = DBPROPOPTIONS_OPTIONAL;
    aProp[cProp].dwStatus     = 0;         // Ignored
    aProp[cProp].colid        = dbcolNull;
    aProp[cProp].vValue.vt    = VT_BOOL;
    aProp[cProp].vValue.boolVal  = _fAllowEnumeration ? VARIANT_FALSE :
                                                        VARIANT_TRUE;

    aPropSet[cProp].rgProperties = &aProp[cProp];
    aPropSet[cProp].cProperties = 1;
    aPropSet[cProp].guidPropertySet = guidQueryExt;

    cProp++;

    // Set the property for retrieving hit count

    Win4Assert( cProp < MAX_PROPS );
    ixssoDebugOut(( DEB_TRACE, "\tNlHitCount = %s\n",
                    ( _dwOptimizeFlags & eOptHitCount ) ? "TRUE" :
                                                          "FALSE" ));

    aProp[cProp].dwPropertyID = NLDBPROP_GETHITCOUNT;
    aProp[cProp].dwOptions    = DBPROPOPTIONS_OPTIONAL;
    aProp[cProp].dwStatus     = 0;         // Ignored
    aProp[cProp].colid        = dbcolNull;
    aProp[cProp].vValue.vt    = VT_BOOL;
    aProp[cProp].vValue.boolVal  = 
              ( _dwOptimizeFlags & eOptHitCount ) ? VARIANT_TRUE :
                                                    VARIANT_FALSE;

    aPropSet[cProp].rgProperties = &aProp[cProp];
    aPropSet[cProp].cProperties = 1;
    aPropSet[cProp].guidPropertySet = DBPROPSET_NLCOMMAND;

    iHitCountProp = cProp;

    cProp++;

    if ( _dwOptimizeFlags & eOptPerformance )
    {
        // Set the property for magically fast queries

        Win4Assert( cProp < MAX_PROPS );
        ixssoDebugOut(( DEB_TRACE, "\tCiDeferNonIndexedTrimming = TRUE\n" ));

        aProp[cProp].dwPropertyID = DBPROP_DEFERNONINDEXEDTRIMMING;
        aProp[cProp].dwOptions    = DBPROPOPTIONS_OPTIONAL;
        aProp[cProp].dwStatus     = 0;         // Ignored
        aProp[cProp].colid        = dbcolNull;
        aProp[cProp].vValue.vt    = VT_BOOL;
        aProp[cProp].vValue.boolVal  = VARIANT_TRUE;

        aPropSet[cProp].rgProperties = &aProp[cProp];
        aPropSet[cProp].cProperties = 1;
        aPropSet[cProp].guidPropertySet = guidQueryExt;

        cProp++;
    }

    // set the start hit property if it is set
    if ( _StartHit.Get() )
    {
        // Set the start hit property
        Win4Assert( cProp < MAX_PROPS );
        ixssoDebugOut(( DEB_TRACE, "\tStartHit = %x\n", _StartHit.Get() ));
        
        aProp[cProp].dwPropertyID           = NLDBPROP_STARTHIT;
        aProp[cProp].dwOptions              = 0;
        aProp[cProp].dwStatus               = 0;         // Ignored
        aProp[cProp].colid                  = dbcolNull;
        V_VT(&(aProp[cProp].vValue))        = VT_ARRAY | VT_I4;
        V_ARRAY(&(aProp[cProp].vValue))     = _StartHit.Get();

        aPropSet[cProp].rgProperties = &aProp[cProp];
        aPropSet[cProp].cProperties = 1;
        aPropSet[cProp].guidPropertySet = DBPROPSET_NLCOMMAND;

        cProp++;
    }

    if ( 0 != _iResourceFactor )
    {
        // Set the query timeout in milliseconds

        Win4Assert( cProp < MAX_PROPS );
        aProp[cProp].dwPropertyID = DBPROP_COMMANDTIMEOUT;
        aProp[cProp].dwOptions    = DBPROPOPTIONS_OPTIONAL;
        aProp[cProp].dwStatus     = 0;         // Ignored
        aProp[cProp].colid        = dbcolNull;
        aProp[cProp].vValue.vt    = VT_I4;
        aProp[cProp].vValue.lVal  = _iResourceFactor;

        aPropSet[cProp].rgProperties = &aProp[cProp];
        aPropSet[cProp].cProperties = 1;
        aPropSet[cProp].guidPropertySet = guidRowsetProps;

        cProp++;
    }

    if ( cProp > 0 )
    {

        ICommandProperties * pCmdProp = 0;
        sc = xICommand->QueryInterface(IID_ICommandProperties,
                                       (void **)&pCmdProp);

        if (FAILED (sc) )
        {
            THROW( CException( QUERY_EXECUTE_FAILED ) );
        }

        sc = pCmdProp->SetProperties( cProp, aPropSet );
        pCmdProp->Release();

        if ( DB_S_ERRORSOCCURRED == sc ||
             DB_E_ERRORSOCCURRED == sc )
        {
            // Ignore an 'unsupported' error trying to set the GetHitCount
            // property.

            unsigned cErrors = 0;
            for (unsigned i = 0; i < cProp; i++)
            {
                if ( i == iHitCountProp &&
                     aProp[i].dwStatus == DBPROPSTATUS_NOTSUPPORTED )
                    continue;

                if (aProp[i].dwStatus != DBPROPSTATUS_OK)
                    cErrors++;
            }
            if ( 0 == cErrors )
                sc = S_OK;
        }

        if ( FAILED(sc) || DB_S_ERRORSOCCURRED == sc )
        {
            THROW( CException( QUERY_EXECUTE_FAILED ) );
        }
    }
    //
    //  Execute the query
    //
    sc = xICommand->Execute( 0,                    // No aggr
                          IsSequential() ? IID_IRowset : IID_IRowsetExactScroll,
                          0,                    // disp params
                          0,            // # rowsets returned
                          (IUnknown **) &_pIRowset );


    if ( FAILED(sc) )
    {
        ERRORINFO ErrorInfo;
        XInterface<IErrorInfo> xErrorInfo;
        SCODE sc2 = GetOleDBErrorInfo(xICommand.GetPointer(),
                                       IID_ICommand,
                                       _lcid,
                                       eMostDetailedCIError,
                                       &ErrorInfo,
                                       (IErrorInfo **)xErrorInfo.GetQIPointer());
        // Post IErrorInfo only if we have a valid ptr to it
        if (SUCCEEDED(sc2) && 0 != xErrorInfo.GetPointer())
        {
            sc = ErrorInfo.hrError;
            THROW( CPostedOleDBException(sc, xErrorInfo.GetPointer()) );
        }
        else
            THROW( CException(sc) );
    }

    xICommand.Acquire()->Release();

    //
    //  Create some of the restriction specific variables.
    //

    //
    // Get _pIRowsetQueryStatus interface
    //
    sc = _pIRowset->QueryInterface( IID_IRowsetQueryStatus,
                                    (void **) &_pIRowsetQueryStatus );

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

    Win4Assert( 0 != _pIRowsetQueryStatus );
}

//+---------------------------------------------------------------------------
//
//  Member:     CixssoQuery::GetQueryStatus - private
//
//  Synopsis:   If a query is active, returns the query status
//
//  Arguments:  NONE
//
//  Returns:    DWORD - query status
//
//  History:    15 Nov 1996      Alanw    Created
//
//----------------------------------------------------------------------------

DWORD CixssoQuery::GetQueryStatus( )
{
    DWORD dwStatus = 0;

    SCODE sc;
    if ( ! _pIRowsetQueryStatus )
        THROW( CIxssoException(MSG_IXSSO_NO_ACTIVE_QUERY, 0) );

    sc = _pIRowsetQueryStatus->GetStatus( &dwStatus );
    if ( ! SUCCEEDED(sc) )
        THROW( CException( sc ) );

    return dwStatus;
}


//+---------------------------------------------------------------------------
//
//  Member:     CixssoQuery::IsAVirtualPath - private
//
//  Synopsis:   Determines if the path passed is a virtual or physical path.
//              If it's a virtual path, then / are changed to \.
//
//  History:    96-Feb-14   DwightKr    Created
//
//----------------------------------------------------------------------------

BOOL CixssoQuery::IsAVirtualPath( WCHAR * wcsPath )
{
    Win4Assert ( 0 != wcsPath );
    if ( 0 == wcsPath[0] )
    {
        return TRUE;
    }

    if ( (L':' == wcsPath[1]) || (L'\\' == wcsPath[0]) )
    {
        return FALSE;
    }
    else
    {
        //
        //  Flip slashes to backslashes
        //

        for ( WCHAR *wcsLetter = wcsPath;
              *wcsLetter != 0;
              wcsLetter++
            )
        {
            if ( L'/' == *wcsLetter )
            {
                *wcsLetter = L'\\';
            }
        }

    }

    return TRUE;
}