windows-nt/Source/XPSP1/NT/enduser/stuff/itircl/query/indeximp.cpp

683 lines
19 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*************************************************************************
* @doc SHROOM EXTERNAL API *
* *
* INDEXIMP.CPP *
* *
* Copyright (C) Microsoft Corporation 1997 *
* All Rights reserved. *
* *
* This file contains the implementation of the index object *
* *
* *
**************************************************************************
* *
* Written By : Erin Foxford *
* Current Owner: erinfox *
* *
**************************************************************************/
#include <mvopsys.h>
#ifdef _DEBUG
static char s_aszModule[] = __FILE__; /* For error report */
#endif
#include <atlinc.h>
// MediaView (InfoTech) includes
#include <groups.h>
#include <wwheel.h>
#include <itquery.h>
#include <itcat.h>
#include <itwbrk.h>
#include <ccfiles.h>
#include <itwbrkid.h>
#include "indeximp.h"
#include "queryimp.h"
#include "mvsearch.h"
#include <ITDB.h>
#include <itcc.h> // for STDPROP_UID def.
#include <itrs.h> // for IITResultSet def.
#include <itgroup.h>
#define QUERYRESULT_GROUPCREATE 0x0800
//----------------------------------------------------------------------
// REVIEW (billa): Need to add critical section locking to all methods
// that reference member variables.
//----------------------------------------------------------------------
/********************************************************************
* @method STDMETHODIMP | IITIndex | Open |
* Opens a full-text index, which can reside in the database's
* storage or as a Win32 file.
*
* @parm IITDatabase* | pITDB | Pointer to database associated with
* full-text index
* @parm LPCWSTR | lpszIndexMoniker | Name of full-text index to open.
* If index resides outside database (as a file), this should include
* the full path to the index.
* @parm BOOL | fInside | If TRUE, index resides inside of database;
* otherwise, index resides outside of database.
*
* @rvalue S_OK | The index was successfully opened
********************************************************************/
STDMETHODIMP CITIndexLocal::Open(IITDatabase* pITDB, LPCWSTR lpszIndexMoniker,
BOOL fInside)
{
HFPB hfpb = NULL;
HRESULT hr;
INDEXINFO indexinfo;
char szFileName[_MAX_PATH + 1] = SZ_FI_STREAM_A;
if (m_idx)
return (SetErrReturn(E_ALREADYINIT));
// We have to have a database for charmap (and stoplist and
// operator table eventually)
if (NULL == pITDB || NULL == lpszIndexMoniker)
return (SetErrReturn(E_INVALIDARG));
m_cs.Lock();
// if index is inside storage, need to get hfpb
if (fInside)
{
WCHAR rgwch[1];
IStorage *pStorageDBRoot = NULL;
// Get root storage from database.
rgwch[0] = (WCHAR) NULL;
if (FAILED(hr = pITDB->GetObjectPersistence(rgwch, IITDB_OBJINST_NULL,
(LPVOID *)&pStorageDBRoot, FALSE)) ||
(hfpb = (HFPB)FpbFromHfs(pStorageDBRoot, &hr)) == NULL)
{
if (pStorageDBRoot != NULL)
pStorageDBRoot->Release();
m_cs.Unlock();
return (hr);
}
}
// TODO: make MVIndexOpen take Unicode file name. This might take a little
// work because it depends on FileOpen, which has a call to one of the Fm
// functions...
DWORD dwSize = (DWORD) STRLEN(szFileName);
WideCharToMultiByte(CP_ACP, 0, lpszIndexMoniker, -1,
szFileName + dwSize, _MAX_PATH + 1 - dwSize, NULL, NULL);
if (NULL == (m_idx = MVIndexOpen(hfpb, (LSZ) szFileName, &hr)))
goto cleanup;
MVGetIndexInfoLpidx(m_idx, &indexinfo);
if (SUCCEEDED(hr = pITDB->GetObject(indexinfo.dwBreakerInstID,
IID_IWordBreaker, (LPVOID *) &m_piwbrk)))
{
BOOL fLicense;
hr = m_piwbrk->Init(TRUE, CB_MAX_WORD_LEN, &fLicense);
}
if (FAILED(hr))
goto cleanup;
// Open catalog object - we only need one instance
// TODO (evaluate): how bad of hit is this going to be?
hr = CoCreateInstance(CLSID_IITCatalogLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITCatalog,
(VOID **) &m_pCatalog);
if (FAILED(hr))
goto cleanup;
// if it fails, there is no catalog which we can run without.
if (FAILED(m_pCatalog->Open(pITDB)))
{
m_pCatalog->Release();
m_pCatalog = NULL;
}
cleanup:
if (FAILED(hr))
Close();
// If we have an HFPB for the DB's root storage, we need to release the
// storage pointer and free the HFPB. FileClose takes care of everything.
if (hfpb)
FileClose(hfpb);
m_cs.Unlock();
return hr;
}
/********************************************************************
* @method STDMETHODIMP | IITIndex | CreateQueryInstance |
* Creates a query object
*
* @parm IITQuery** | ppITQuery | Indirect pointer to query object
*
* @rvalue S_OK | The query object was successfully returned
*
********************************************************************/
STDMETHODIMP CITIndexLocal::CreateQueryInstance(IITQuery** ppITQuery)
{
// TODO: possible optimization in case where user specifies multiple
// query objects... get class factory pointer once; then call CreateInstance
// Free CF when all done w/ index object.
return CoCreateInstance(CLSID_IITQuery, NULL, CLSCTX_INPROC_SERVER, IID_IITQuery,
(VOID **) ppITQuery);
}
/********************************************************************
* @method STDMETHODIMP | IITIndex | Search |
* Performs a full-text search on the open index, returning the
* results in a result set object.
*
* @parm IITQuery* | pITQuery | Pointer to query object
* @parm IITResultSet* | pITResult | Pointer to result set object
* containing search results. Caller is responsible for initializing
* the result set with the properties to be returned.
*
* @rvalue S_FALSE | The search was successful, but returned no hits.
* @rvalue S_OK | The search was successfully performed.
* @rvalue E_NOTOPEN | The index object is not open.
* @rvalue E_INVALIDARG | One or both parameters is NULL.
* @rvalue E_OUTOFMEMORY | There was not enough memory to perform this function.
* @rvalue E_NULLQUERY | The query consisted of no terms, or is all stopwords.
* @rvalue E_STOPWORD | A stopword was one of the terms in the query.
* @rvalue E_* | An error occurred during the search. Check iterror.h for the possible error codes.
*
* @comm The caller is responsible for setting the proper options
* through the query object before calling this function.
********************************************************************/
STDMETHODIMP CITIndexLocal::Search(IITQuery* pITQuery, IITResultSet* pITResult)
{
HRESULT hr;
CITIndexObjBridge *pidxobr = NULL;
LPQT pQueryTree = NULL; // Pointer to query tree
LPHL pHitList = NULL; // Pointer to hit list
IITGroup* piitGroup = NULL;
_LPGROUP lpGroup;
if (NULL == pITQuery || NULL == pITResult)
return (SetErrReturn(E_INVALIDARG));
if (m_idx == NULL)
return (SetErrReturn(E_NOTOPEN));
if ((pidxobr = new CITIndexObjBridge) != NULL)
{
pidxobr->AddRef();
hr = pidxobr->SetWordBreaker(m_piwbrk);
}
else
hr = E_OUTOFMEMORY;
if (SUCCEEDED(hr) &&
SUCCEEDED(hr = QueryParse(pITQuery, &pQueryTree, pidxobr)))
{
SRCHINFO SrchInfo; // Search parameters
SrchInfo.dwMemAllowed = 0;
pITQuery->GetResultCount((LONG &)SrchInfo.dwTopicCount);
pITQuery->GetOptions(SrchInfo.Flag);
SrchInfo.dwValue = 0;
SrchInfo.dwTopicFullCalc = 0;
SrchInfo.lpvIndexObjBridge = (LPVOID) pidxobr;
pITQuery->GetGroup(&piitGroup);
if (piitGroup)
lpGroup = (_LPGROUP)piitGroup->GetLocalImageOfGroup();
else
lpGroup = NULL;
// Perform search
pHitList = MVIndexSearch(m_idx, pQueryTree, &SrchInfo, lpGroup, &hr);
// Massage hitlist into a result set.
if (pHitList)
{
hr = HitListToResultSet(pHitList, pITResult, pidxobr);
MVHitListDispose(pHitList);
}
}
if (pQueryTree)
MVQueryFree(pQueryTree);
// We don't want to delete pidxobr if HitListToResultSet AddRef'ed it
// so that the result set can hold onto a term string heap via pidxobr.
if (pidxobr && pidxobr->Release() == 0)
delete pidxobr;
return hr;
}
/********************************************************************
* @method STDMETHODIMP | IITIndex | Search |
* Performs a full-text search on the open index, returning the
* results in a group object.
*
* @parm IITQuery* | pITQuery | Pointer to query object
* @parm IITGroup* | pITGroup | Pointer to group object. The caller
* is responsible for initializing this object before passing it.
*
* @rvalue S_OK | The search was successfully performed
*
* @comm The caller is responsible for setting the proper options
* through the query object before calling this function.
********************************************************************/
STDMETHODIMP CITIndexLocal::Search(IITQuery* pITQuery, IITGroup* pITGroup)
{
HRESULT hr = S_OK;
CITIndexObjBridge *pidxobr = NULL;
LPQT pQueryTree = NULL; // Pointer to query tree
LPHL pHitList = NULL; // Pointer to hit list
if (NULL == pITQuery || NULL == pITGroup)
return (SetErrReturn(E_INVALIDARG));
if (m_idx == NULL)
return (SetErrReturn(E_NOTOPEN));
// TODO: MVIndexSearch would take IITGroup*, not _LPGROUP
_LPGROUP lpGroup = (_LPGROUP) pITGroup->GetLocalImageOfGroup();
if ((pidxobr = new CITIndexObjBridge) != NULL)
hr = pidxobr->SetWordBreaker(m_piwbrk);
else
hr = E_OUTOFMEMORY;
if (SUCCEEDED(hr) &&
SUCCEEDED(hr = QueryParse(pITQuery, &pQueryTree, pidxobr)))
{
SRCHINFO SrchInfo; // Search parameters
SrchInfo.dwMemAllowed = 0;
pITQuery->GetResultCount((LONG &)SrchInfo.dwTopicCount);
pITQuery->GetOptions(SrchInfo.Flag);
SrchInfo.Flag |= QUERYRESULT_GROUPCREATE;
SrchInfo.dwValue = 0;
SrchInfo.dwTopicFullCalc = 0;
SrchInfo.lpvIndexObjBridge = (LPVOID) pidxobr;
// Perform search - if pHitList comes back NULL, we will return hr
if (pHitList = MVIndexSearch(m_idx, pQueryTree, &SrchInfo, lpGroup, &hr))
MVHitListDispose(pHitList);
}
if (pQueryTree)
MVQueryFree(pQueryTree);
if (pidxobr)
delete pidxobr;
return hr;
}
// This is private - it encapsulates the query parsing needed
// in all searches
STDMETHODIMP CITIndexLocal::QueryParse(IITQuery* pITQuery, LPQT* pQueryTree,
CITIndexObjBridge *pidxobr)
{
HRESULT hr = S_OK;
EXBRKPM exbrkpm;
PARSE_PARMS ParseParm;
ITASSERT(pITQuery != NULL && pQueryTree != NULL && pidxobr != NULL);
// Fill PARSE_PARMS structure
DWORD dwFlags;
pITQuery->GetOptions(dwFlags);
if (dwFlags & QUERYRESULT_SKIPOCCINFO)
m_fSkipOcc = TRUE;
ParseParm.cDefOp = (WORD)(dwFlags & IMPLICIT_OR);
ParseParm.wCompoundWord = (WORD)(dwFlags & COMPOUNDWORD_PHRASE);
pITQuery->GetProximity(ParseParm.cProxDist);
IITGroup* ITGroup;
pITQuery->GetGroup(&ITGroup);
if (ITGroup)
{
_LPGROUP lpGroup = (_LPGROUP) ITGroup->GetLocalImageOfGroup();
ParseParm.lpGroup = lpGroup;
}
else
ParseParm.lpGroup = NULL;
// Breaker bridge setup
exbrkpm.lpvIndexObjBridge = (LPVOID)pidxobr;
ParseParm.pexbrkpm = &exbrkpm;
// TODO: provide the right stuff
ParseParm.lpOpTab = NULL;
LPSTR lpszQuery = NULL; // Pointer to query buffer
DWORD cbQuery; // Query buffer's length
DWORD dwCodePageID;
LCID lcid;
if (FAILED(GetLocaleInfo(&dwCodePageID, &lcid)))
{
ITASSERT(FALSE);
dwCodePageID = CP_ACP;
}
LPCWSTR lpszwQuery;
pITQuery->GetCommand(lpszwQuery);
if (NULL == lpszwQuery)
return E_NULLQUERY;
// Query comes in as Unicode, but the FTI still uses MBCS.
cbQuery = WideCharToMultiByte
(dwCodePageID, 0, lpszwQuery, -1, NULL, 0, NULL, NULL);
if ((lpszQuery = new char[cbQuery]) != NULL)
{
WideCharToMultiByte(dwCodePageID, 0, lpszwQuery, -1, lpszQuery, cbQuery,
NULL, NULL);
ParseParm.cbQuery = cbQuery - 1;
ParseParm.lpbQuery = (const char*) lpszQuery;
}
else
hr = E_OUTOFMEMORY;
// Parse query
if (SUCCEEDED(hr))
{
FCALLBACK_MSG fcbkmsg;
*pQueryTree = MVQueryParse (&ParseParm, &hr);
if (SUCCEEDED(hr) && SUCCEEDED(pITQuery->GetResultCallback(&fcbkmsg)))
MVSearchSetCallback(*pQueryTree, &fcbkmsg);
}
if (lpszQuery)
delete lpszQuery;
return hr;
}
/********************************************************************
* @method STDMETHODIMP | IITIndex | Close |
* Closes the full-text index.
*
* @rvalue S_OK | The index was successfully closed
*
********************************************************************/
STDMETHODIMP CITIndexLocal::Close()
{
m_cs.Lock();
if (m_idx)
{
MVIndexClose(m_idx);
m_idx = NULL;
}
if (m_pCatalog)
{
m_pCatalog->Close();
m_pCatalog->Release();
m_pCatalog = NULL;
}
if (m_piwbrk != NULL)
{
m_piwbrk->Release();
m_piwbrk = NULL;
}
m_cs.Unlock();
return S_OK;
}
/********************************************************************
* @method STDMETHODIMP | IITIndex | GetLocaleInfo |
* Gets locale info that the full text index was built with.
* @parm DWORD* | pdwCodePageID | On exit, pointer to code page ID.
* @parm LCID* | plcid | On exit, pointer to locale ID.
*
* @rvalue S_OK | The locale info was successfully retrieved.
*
********************************************************************/
STDMETHODIMP CITIndexLocal::GetLocaleInfo(DWORD *pdwCodePageID, LCID *plcid)
{
INDEXINFO indexinfo;
if (pdwCodePageID == NULL || plcid == NULL)
return (SetErrReturn(E_POINTER));
if (m_idx == NULL)
return (SetErrReturn(E_NOTOPEN));
MVGetIndexInfoLpidx(m_idx, &indexinfo);
*pdwCodePageID = indexinfo.dwCodePageID;
*plcid = indexinfo.lcid;
return (S_OK);
}
/********************************************************************
* @method STDMETHODIMP | IITIndex | GetWordBreakerInstance |
* Gets the ID of the word breaker instance that the full text
* index was built with.
* @parm DWORD* | pdwObjInstance | On exit, pointer to word breaker instance.
*
* @rvalue S_OK | The word breaker instance ID was successfully retrieved.
*
********************************************************************/
STDMETHODIMP CITIndexLocal::GetWordBreakerInstance(DWORD *pdwObjInstance)
{
INDEXINFO indexinfo;
if (pdwObjInstance == NULL)
return (SetErrReturn(E_POINTER));
if (m_idx == NULL)
return (SetErrReturn(E_NOTOPEN));
MVGetIndexInfoLpidx(m_idx, &indexinfo);
*pdwObjInstance = indexinfo.dwBreakerInstID;
return (S_OK);
}
// Private function - passed as a parameter by
// CITIndexLocal::HitListToResultSet.
SCODE __stdcall FreeRSColumnHeap(LPVOID lpvIndexObjBridge)
{
CITIndexObjBridge *pidxobr;
if (lpvIndexObjBridge == NULL)
return (SetErrReturn(E_POINTER));
pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
pidxobr->Release();
delete pidxobr;
return (S_OK);
}
// Private function - one grand hack to provide a result set from a hit list
STDMETHODIMP CITIndexLocal::HitListToResultSet(LPHL pHitList, IITResultSet* pRS,
CITIndexObjBridge *pidxobr)
{
DWORD cEntry; // Number of entries
HIT HitInfo;
TOPICINFO TopicInfo;
LONG lColumnUID = -1;
LONG lColumnOccInfo[5];
DWORD iTopic, iHit, iColumn; // Loop indices
LONG lRow = 0;
HRESULT hr;
ITASSERT(pRS != NULL && pidxobr != NULL);
// Number of entries in hit list - if 0, just return FALSE
if (0 == (cEntry = MVHitListEntries(pHitList)))
return S_FALSE;
hr = pRS->GetColumnFromPropID(STDPROP_UID, lColumnUID);
if (!m_fSkipOcc)
{
for (iColumn = 0; iColumn < 5; iColumn++)
lColumnOccInfo[iColumn] = -1;
pRS->GetColumnFromPropID(STDPROP_FIELD, lColumnOccInfo[0]);
pRS->GetColumnFromPropID(STDPROP_LENGTH, lColumnOccInfo[1]);
pRS->GetColumnFromPropID(STDPROP_COUNT, lColumnOccInfo[2]);
pRS->GetColumnFromPropID(STDPROP_OFFSET, lColumnOccInfo[3]);
pRS->GetColumnFromPropID(STDPROP_TERM_UNICODE_ST, lColumnOccInfo[4]);
}
// Loop over all the topics in the hit list
for (iTopic = 0; iTopic < cEntry; iTopic++)
{
hr = MVHitListGetTopic(pHitList, iTopic, &TopicInfo);
if (FAILED(hr))
return hr; // or do we continue?
if (m_fSkipOcc)
{
// No occurrence info
if (-1 != lColumnUID)
pRS->Set(lRow, lColumnUID, TopicInfo.dwTopicId);
lRow++;
}
else
{
// Requested occurence info, so loop
// over all the hits in this topic
for (iHit = 0; iHit < TopicInfo.lcHits; iHit++)
{
if (-1 != lColumnUID)
pRS->Set(lRow, lColumnUID, TopicInfo.dwTopicId);
hr = MVHitListGetHit(pHitList, &TopicInfo, iHit, &HitInfo);
if (FAILED(hr))
continue;
if (-1 != lColumnOccInfo[0])
pRS->Set(lRow, lColumnOccInfo[0], HitInfo.dwFieldId);
if (-1 != lColumnOccInfo[1])
pRS->Set(lRow, lColumnOccInfo[1], HitInfo.dwLength);
if (-1 != lColumnOccInfo[2])
pRS->Set(lRow, lColumnOccInfo[2], HitInfo.dwCount);
if (-1 != lColumnOccInfo[3])
pRS->Set(lRow, lColumnOccInfo[3], HitInfo.dwOffset);
if (-1 != lColumnOccInfo[4])
pRS->Set(lRow, lColumnOccInfo[4], (DWORD_PTR) HitInfo.lpvTerm);
lRow++;
}
}
}
// Fill in rest of properties from catalog (like IITWordWheel::GetData)
if (m_pCatalog)
{
hr = m_pCatalog->Lookup(pRS);
if (S_FALSE == hr)
hr = S_OK; // don't report S_FALSE
}
// If the caller requested Unicode term STs, then we need to give the result
// set the string heap and adjust the string lengths in the heap. Otherwise,
// we will just let the heap get freed whenever pidxobr gets deleted.
if (-1 != lColumnOccInfo[4])
{
pidxobr->AdjustQueryResultTerms();
pRS->SetColumnHeap(lColumnOccInfo[4], (LPVOID) pidxobr, FreeRSColumnHeap);
// Tell our caller not to delete pidxobr because the result set is
// holding onto it.
pidxobr->AddRef();
}
return S_OK;
}
// Need to export these without decoration to the linker so they can be called
// from the old .c files.
extern "C" {
PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtBreakText(PEXBRKPM pexbrkpm)
{
CITIndexObjBridge *pidxobr;
if (pexbrkpm == NULL || pexbrkpm->lpvIndexObjBridge == NULL)
return (SetErrReturn(E_POINTER));
pidxobr = (CITIndexObjBridge *) pexbrkpm->lpvIndexObjBridge;
return (pidxobr->BreakText(pexbrkpm));
}
PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtStemWord(LPVOID lpvIndexObjBridge, LPBYTE lpbStemWord, LPBYTE lpbRawWord)
{
CITIndexObjBridge *pidxobr;
if (lpvIndexObjBridge == NULL ||
lpbStemWord == NULL || lpbRawWord == NULL)
return (SetErrReturn(E_POINTER));
pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
return (pidxobr->StemWord(lpbStemWord, lpbRawWord));
}
PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtLookupStopWord(LPVOID lpvIndexObjBridge, LPBYTE lpbStopWord)
{
CITIndexObjBridge *pidxobr;
if (lpvIndexObjBridge == NULL || lpbStopWord == NULL)
return (SetErrReturn(E_POINTER));
pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
return (pidxobr->LookupStopWord(lpbStopWord));
}
PUBLIC HRESULT EXPORT_API FAR PASCAL
ExtAddQueryResultTerm(LPVOID lpvIndexObjBridge, LPBYTE lpbTermHit,
LPVOID *ppvTermHit)
{
CITIndexObjBridge *pidxobr;
if (lpvIndexObjBridge == NULL || lpbTermHit == NULL || ppvTermHit == NULL)
return (SetErrReturn(E_POINTER));
pidxobr = (CITIndexObjBridge *) lpvIndexObjBridge;
return (pidxobr->AddQueryResultTerm(lpbTermHit, ppvTermHit));
}
} // End extern "C"