2025-04-27 07:49:33 -04:00

568 lines
19 KiB
C++

#define INC_OLE2
#define UNICODE
#include <stdio.h>
#include <windows.h>
#define OLEDBVER 0x0250 // enable ICommandTree interface
#include <oledberr.h>
#include <oledb.h>
#include <cmdtree.h>
#include <oledbdep.h>
#include <query.h>
#include <ntquery.h>
#include <stgprop.h>
#define NTSTATUS HRESULT
#include <dbcmdtre.hxx>
#include <vquery.hxx>
#include <isquery.h>
#include <dbgtrace.h>
// {AA568EEC-E0E5-11cf-8FDA-00AA00A14F93}
GUID CLSID_NNTP_SummaryInformation =
{ 0xaa568eec, 0xe0e5, 0x11cf, { 0x8f, 0xda, 0x0, 0xaa, 0x0, 0xa1, 0x4f, 0x93 } };
static GUID guidSystem = PSGUID_STORAGE;
static GUID guidNews = CLSID_NNTP_SummaryInformation;
static CDbColId psNewsgroup(guidNews, 0x2);
static CDbColId psNewsArticleID(guidNews, 0x3c);
static CDbColId psNewsMessageID(guidNews, 0x7);
static CDbColId psFilename(guidSystem, 0xa);
static CDbColId psNewsFrom(guidNews, 0x6);
static CDbColId psNewsSubject(guidNews, 0x5);
static struct {
WCHAR wszColumnName[16];
CDbColId *pdbidColumn;
} rgColumnMap[] = {
{ L"filename", &psFilename },
{ L"newsarticleid", &psNewsArticleID },
{ L"newsfrom", &psNewsFrom },
{ L"newsgroup", &psNewsgroup },
{ L"newsmsgid", &psNewsMessageID },
{ L"newssubject", &psNewsSubject },
{ NULL, 0 }
};
static DBBINDING skelbinding = {
0,4*0,0,0,0,0,0, DBPART_VALUE, DBMEMOWNER_PROVIDEROWNED,
DBPARAMIO_NOTPARAM, 0, 0, DBTYPE_VARIANT|DBTYPE_BYREF, 0, 0
};
CIndexServerQuery::CIndexServerQuery() {
m_pRowset = NULL;
m_hAccessor = NULL;
m_cRowHandlesInUse = 0;
m_phRows = NULL;
m_cPropDef = 0;
m_pPropDef = 0;
}
//
// build an Accessor for the rowset given the names of the rows that we
// are interested in.
//
// Arguments:
// [in] wszCols - the columns that you are interested in, comma delimited
//
HRESULT CIndexServerQuery::CreateAccessor(WCHAR *szColumns) {
TraceFunctEnter("CIndexServerQuery::CreateAccessor");
IColumnsInfo *pColumnsInfo = NULL;
DBBINDING rgBindings[MAX_COLUMNS];
IAccessor *pIAccessor = NULL;
DBID rgColumnIDs[MAX_COLUMNS];
ULONG rgMappedColumnIDs[MAX_COLUMNS];
DWORD i, cCols;
WCHAR *rgszColumn[MAX_COLUMNS];
//
// find the column names
//
cCols = 1;
rgszColumn[0] = szColumns;
for (i = 0; szColumns[i] != 0; i++) {
if (szColumns[i] == ',') {
// check to make sure we don't overflow rgszColumn
if (cCols == MAX_COLUMNS) {
ErrorTrace((DWORD) this, "too many columns passed into CreateAccessor");
TraceFunctLeave();
return E_INVALIDARG;
}
rgszColumn[cCols] = szColumns + i + 1;
if (*rgszColumn[cCols] == 0) {
ErrorTrace((DWORD) this, "trailing comma found in szColumns");
TraceFunctLeave();
return E_INVALIDARG;
}
cCols++;
szColumns[i] = 0;
}
}
//
// map the column names passed in by the user into column IDs
//
DebugTrace((DWORD) this, "%i columns in szColumns", cCols);
for (i = 0; i < cCols; i++) {
DWORD j;
for (j = 0; rgColumnMap[j].wszColumnName != NULL; j++) {
DWORD x = lstrcmpi(rgColumnMap[j].wszColumnName, rgszColumn[i]);
if (x == 0) {
rgColumnIDs[i] = *(rgColumnMap[j].pdbidColumn);
DebugTrace((DWORD) this, "Column %i is %ws", i, rgszColumn[i]);
break;
}
}
// check to make sure that we found a matching column
if (rgColumnMap[j].wszColumnName == NULL) {
ErrorTrace((DWORD) this, "unsupported column %ws in szColumns", rgszColumn[i]);
TraceFunctLeave();
return E_INVALIDARG;
}
}
//
// get a IColumnsInfo interface and use it to map the column IDs
//
HRESULT hr = m_pRowset->QueryInterface(IID_IColumnsInfo, (void **)&pColumnsInfo);
if (FAILED(hr)) {
ErrorTrace((DWORD) this, "QI(IID_IColumnsInfo) returned 0x%08x", hr);
TraceFunctLeave();
return hr;
}
hr = pColumnsInfo->MapColumnIDs(cCols, rgColumnIDs, rgMappedColumnIDs);
pColumnsInfo->Release();
if (FAILED(hr)) {
ErrorTrace((DWORD) this, "MapColumnIDs returned 0x%08x", hr);
TraceFunctLeave();
return hr;
}
//
// build up the binding array
//
for (i = 0; i < cCols; i++) {
memcpy(&(rgBindings[i]), &(skelbinding), sizeof(DBBINDING));
rgBindings[i].obValue = 4 * i;
rgBindings[i].iOrdinal = rgMappedColumnIDs[i];
}
//
// get the IAccessor interface and use that to build an accessor to
// these columns.
//
hr = m_pRowset->QueryInterface( IID_IAccessor, (void **)&pIAccessor);
if (FAILED(hr)) {
ErrorTrace((DWORD) this, "QI(IID_IAccessor) returned 0x%08x", hr);
TraceFunctLeave();
return hr;
}
hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
cCols, rgBindings, 0, &m_hAccessor, 0 );
pIAccessor->Release();
m_cCols = cCols;
DebugTrace((DWORD) this, "returning 0x%08x", hr);
TraceFunctLeave();
return hr;
}
//
// release's an accessor that was created with CreateAccessor
//
void CIndexServerQuery::ReleaseAccessor() {
TraceFunctEnter("CIndexServerQuery::ReleaseAccessor");
IAccessor * pIAccessor = 0;
HRESULT hr = m_pRowset->QueryInterface(IID_IAccessor, (void **)&pIAccessor);
if (FAILED(hr)) {
DebugTrace((DWORD) this, "QI(IID_IAccessor) returned 0x%08x", hr);
TraceFunctLeave();
return;
}
hr = pIAccessor->ReleaseAccessor( m_hAccessor, 0 );
if (FAILED(hr)) DebugTrace((DWORD) this, "ReleaseAccessor returned 0x%08x", hr);
m_hAccessor = NULL;
hr = pIAccessor->Release();
if (FAILED(hr)) DebugTrace((DWORD) this, "pAccessor->Release returned 0x%08x", hr);
TraceFunctLeave();
}
//
// scan the query string and see if there are property names that are being
// queried that don't have friendly names. if there are then we build up
// new friendly names
//
#define HEADERPREFIX L"@MSGH-"
#define HEADERPREFIXLEN 6
HRESULT CIndexServerQuery::BuildFriendlyNames(const WCHAR *pwszQueryString) {
TraceFunctEnter("CIndexServerQuery::BuildFriendlyNames");
const WCHAR *pwszHeaderPrefix;
DWORD cFriendlyNames = 0;
// count the number of friendly names in the string
pwszHeaderPrefix = pwszQueryString;
do {
pwszHeaderPrefix = wcsstr(pwszHeaderPrefix, HEADERPREFIX);
if (pwszHeaderPrefix != NULL) {
cFriendlyNames++;
pwszHeaderPrefix += HEADERPREFIXLEN;
}
} while (pwszHeaderPrefix != NULL);
if (cFriendlyNames == 0) return S_OK;
m_pPropDef = new CIPROPERTYDEF[cFriendlyNames];
if (m_pPropDef == NULL) return E_OUTOFMEMORY;
ZeroMemory(m_pPropDef, sizeof(CIPROPERTYDEF) * cFriendlyNames);
pwszHeaderPrefix = wcsstr(pwszQueryString, HEADERPREFIX);
while (pwszHeaderPrefix != NULL) {
// copy from past the Mime to the next space into the friendly name
WCHAR *pwszFriendlyName = new WCHAR[MAX_FRIENDLYNAME];
if (pwszFriendlyName == NULL) {
ErrorTrace((DWORD) this, "couldn't allocate mem for new friendlyname");
TraceFunctLeave();
return E_OUTOFMEMORY;
}
wcscpy(pwszFriendlyName, HEADERPREFIX + 1);
WCHAR *pwszHeaderName = pwszFriendlyName + HEADERPREFIXLEN - 1;
const WCHAR *p = pwszHeaderPrefix + HEADERPREFIXLEN;
for (DWORD i = HEADERPREFIXLEN - 1; *p != 0 && *p != ' '; p++, i++) {
if (i >= MAX_FRIENDLYNAME) {
ErrorTrace((DWORD) this, "friendlyname %S is too long",
pwszHeaderPrefix);
TraceFunctLeave();
return E_INVALIDARG;
}
pwszFriendlyName[i] = *p;
}
pwszFriendlyName[i] = 0;
// see if this property has already been defined
BOOL fFound = FALSE;
for (DWORD m_iPropDef = 0; m_iPropDef < m_cPropDef; m_iPropDef++) {
if (lstrcmpiW(m_pPropDef[m_iPropDef].wcsFriendlyName, pwszFriendlyName)) {
fFound = TRUE;
}
}
// if it hasn't been defined then add it to the list of defined
// properties
if (!fFound) {
// build a new CIPROPERTYDEF
_ASSERT(m_cPropDef <= cFriendlyNames);
m_pPropDef[m_cPropDef].wcsFriendlyName = pwszFriendlyName;
m_pPropDef[m_cPropDef].dbType = DBTYPE_WSTR;
m_pPropDef[m_cPropDef].dbCol.eKind = DBKIND_GUID_NAME;
m_pPropDef[m_cPropDef].dbCol.uGuid.guid = guidNews;
m_pPropDef[m_cPropDef].dbCol.uName.pwszName = pwszHeaderName;
DebugTrace((DWORD) this, "new friendly name %S", pwszFriendlyName);
DebugTrace((DWORD) this, "pwszHeaderName = %S", pwszHeaderName);
BinaryTrace((DWORD) this, (BYTE *) &guidNews, sizeof(guidNews));
m_cPropDef++;
}
// p points to the end of the @MsgH-<header> part, where we might
// expect to find another such word.
pwszHeaderPrefix = wcsstr(p, HEADERPREFIX);
}
DebugTrace((DWORD) this, "defined %lu friendly names", m_cPropDef);
TraceFunctLeave();
return S_OK;
}
//
// make a query
//
// Arguments:
// bDeepQuery - [in] TRUE if deep query, FALSE if shallow
// pwszQueryString - [in] the Tripoli query string
// pwszMachine - [in] the machine to query against (. for localhost)
// pwszCatalog - [in] the catalog to query against
// pwszScope - [in] the scope to query against
// pwszColumns - [in] the columns to return.
// Supports filename, newsgroup, newsarticleid
// pwszSortOrder - [in] how to sort the above columns
//
HRESULT CIndexServerQuery::MakeQuery(BOOL bDeepQuery,
WCHAR const *pwszQueryString,
WCHAR const *pwszMachine,
WCHAR const *pwszCatalog,
WCHAR const *pwszScope,
WCHAR *pwszColumns,
WCHAR const *pwszSortOrder,
LCID LocalID,
DWORD dwMaxRows)
{
TraceFunctEnter("MakeQuery");
ULONG rgDepths[] = { bDeepQuery ? QUERY_DEEP : QUERY_SHALLOW};
WCHAR const *rgScopes[] = { (pwszScope == NULL) ? L"\\" : pwszScope };
WCHAR const *rgCatalogs[] = { pwszCatalog };
WCHAR const *rgMachines[] = { (pwszMachine == NULL) ? L"." : pwszMachine };
ICommand *pCommand = 0;
DebugTrace((DWORD) this, "pwszQueryString = %ws", pwszQueryString);
DebugTrace((DWORD) this, "pwszMachine = %ws", pwszMachine);
DebugTrace((DWORD) this, "pwszCatalog = %ws", pwszCatalog);
DebugTrace((DWORD) this, "pwszScope = %ws", pwszScope);
DebugTrace((DWORD) this, "pwszColumns = %ws", pwszColumns);
DebugTrace((DWORD) this, "pwszSortOrder = %ws", pwszSortOrder);
_ASSERT(pwszColumns != NULL);
if (pwszCatalog == NULL || pwszColumns == NULL) {
ErrorTrace((DWORD) this, "pwszCatalog == NULL or pwszColumns == NULL");
TraceFunctLeave();
return E_POINTER;
}
_ASSERT(m_pRowset == NULL);
if (m_pRowset != NULL) {
ErrorTrace((DWORD) this, "MakeQuery called with pRowset != NULL");
TraceFunctLeave();
return E_UNEXPECTED;
}
HRESULT hr = BuildFriendlyNames(pwszQueryString);
if (FAILED(hr)) return hr;
DebugTrace((DWORD) this, "calling CIMakeICommand");
hr = CIMakeICommand(&pCommand,
1,
rgDepths,
(WCHAR const * const *) rgScopes,
(WCHAR const * const *) rgCatalogs,
(WCHAR const * const *) rgMachines);
if (SUCCEEDED(hr))
{
const unsigned MAX_PROPS = 8;
DBPROPSET aPropSet[MAX_PROPS];
DBPROP aProp[MAX_PROPS];
ULONG cProps = 0;
// We can handle PROPVARIANTs, not just ole automation variants
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;
aProp[cProps].dwPropertyID = DBPROP_USEEXTENDEDDBTYPES;
aProp[cProps].dwOptions = DBPROPOPTIONS_SETIFCHEAP;
aProp[cProps].dwStatus = 0;
aProp[cProps].colid = dbcolNull;
aProp[cProps].vValue.vt = VT_BOOL;
aProp[cProps].vValue.boolVal = VARIANT_TRUE;
aPropSet[cProps].rgProperties = &aProp[cProps];
aPropSet[cProps].cProperties = 1;
aPropSet[cProps].guidPropertySet = guidQueryExt;
cProps++;
ICommandProperties *pCmdProp = 0;
hr = pCommand->QueryInterface(IID_ICommandProperties,
(void **)&pCmdProp);
if (SUCCEEDED(hr)) {
DebugTrace((DWORD) this, "calling SetProperties");
hr = pCmdProp->SetProperties( cProps, aPropSet );
pCmdProp->Release();
}
DBCOMMANDTREE *pTree;
DebugTrace((DWORD) this, "calling CITextToFullTree");
hr = CITextToFullTree(pwszQueryString, // query
pwszColumns, // columns
pwszSortOrder, // sort
0, // grouping
&pTree, // resulting tree
m_cPropDef, // custom properties
m_pPropDef, // custom properties
LocalID); // default locale
if (SUCCEEDED(hr)) {
ICommandTree *pCmdTree = NULL;
hr = pCommand->QueryInterface(IID_ICommandTree,
(void **)&pCmdTree);
DebugTrace((DWORD) this, "calling SetCommandTree");
hr = pCmdTree->SetCommandTree(&pTree, DBCOMMANDREUSE_NONE, FALSE);
pCmdTree->Release();
IRowset *pRowset = 0;
if (SUCCEEDED(hr)) {
DebugTrace((DWORD) this, "calling Execute");
hr = pCommand->Execute(0, // no aggr. IUnknown
IID_IRowset, // IID for i/f to return
0, // disp. params
0, // chapter
(IUnknown **) &pRowset );
if (SUCCEEDED(hr))
{
DebugTrace((DWORD) this, "calling CreateAccessor");
m_pRowset = pRowset;
hr = CreateAccessor(pwszColumns);
m_fNoMoreRows = FALSE;
m_phRows = NULL;
m_cRowHandlesInUse = 0;
m_cRowHandlesAllocated = 0;
}
}
}
pCommand->Release();
}
DebugTrace((DWORD) this, "returning 0x%08x", hr);
TraceFunctLeave();
return hr;
}
//
// Get back some query results after making a query
//
// Arguments:
// pcResults - [in/out] The number of results to retrieve. When the function
// returns is has the number of results retrieved
// ppvResults - [out] The array of propvariant pointers to receive the results
// pfMore - [out] Set to FALSE when there are no more results to retrieve
//
HRESULT CIndexServerQuery::GetQueryResults(DWORD *pcResults,
PROPVARIANT **ppvResults,
BOOL *pfMore)
{
TraceFunctEnter("GetQueryResults");
HRESULT hr;
DWORD cDesiredRows;
DWORD iCurrentRow;
// check to make sure that they've called MakeQuery successfully
if (m_pRowset == NULL) {
ErrorTrace((DWORD) this, "GetQueryResults called without MakeQuery");
TraceFunctLeave();
return E_UNEXPECTED;
}
if (m_fNoMoreRows) {
*pfMore = FALSE;
TraceFunctLeave();
return S_OK;
}
cDesiredRows = *pcResults;
*pcResults = 0;
*pfMore = TRUE;
if (m_cRowHandlesInUse != 0) {
m_pRowset->ReleaseRows(m_cRowHandlesInUse, m_phRows, 0, 0, 0);
m_cRowHandlesInUse = 0;
if (cDesiredRows > m_cRowHandlesAllocated) {
delete[] m_phRows;
m_phRows = NULL;
m_cRowHandlesAllocated = 0;
}
}
// allocate memory for the row handles
if (cDesiredRows > m_cRowHandlesAllocated) {
_ASSERT(m_phRows == NULL);
m_phRows = new HROW[cDesiredRows];
if (m_phRows == NULL) {
DebugTrace((DWORD) this, "out of memory trying to alloc %lu row handles", cDesiredRows);
return E_OUTOFMEMORY;
}
m_cRowHandlesAllocated = cDesiredRows;
}
// fetch some more rows from tripoli
DebugTrace((DWORD) this, "getting more tripoli rows");
hr = m_pRowset->GetNextRows(0,
0,
cDesiredRows,
&m_cRowHandlesInUse,
&m_phRows);
DebugTrace((DWORD) this, "GetNextRows returned %lu rows", m_cRowHandlesInUse);
// check for end of rowset
if (hr == DB_S_ENDOFROWSET) {
DebugTrace((DWORD) this, "GetNextRows returned end of rowset");
hr = S_OK;
m_fNoMoreRows = TRUE;
*pfMore = FALSE;
}
if (FAILED(hr)) {
ErrorTrace((DWORD) this, "GetNextRows failed with 0x%08x", hr);
TraceFunctLeave();
return hr;
}
// get the data for each of the rows
for (iCurrentRow = 0; iCurrentRow < m_cRowHandlesInUse; iCurrentRow++) {
// fetch the data for this row
hr = m_pRowset->GetData(m_phRows[iCurrentRow], m_hAccessor,
ppvResults + ((*pcResults) * m_cCols));
if (FAILED(hr)) {
ErrorTrace((DWORD) this, "GetData failed with 0x%08x", hr);
TraceFunctLeave();
return hr;
} else {
(*pcResults)++;
}
}
DebugTrace((DWORD) this, "*pcResults = %lu, *pfMore = %lu", *pcResults, *pfMore);
TraceFunctLeave();
return hr;
}
CIndexServerQuery::~CIndexServerQuery() {
TraceFunctEnter("CIndexServerQuery");
// clean up any custom named properties
if (m_cPropDef != 0) {
while (m_cPropDef-- != 0) {
delete[] m_pPropDef[m_cPropDef].wcsFriendlyName;
}
delete[] m_pPropDef;
} else {
_ASSERT(m_pPropDef == NULL);
}
if (m_cRowHandlesInUse != 0) {
m_pRowset->ReleaseRows(m_cRowHandlesInUse, m_phRows, 0, 0, 0);
m_cRowHandlesInUse = 0;
}
if (m_phRows != NULL) {
delete[] m_phRows;
m_phRows = NULL;
m_cRowHandlesAllocated = 0;
}
if (m_pRowset != NULL) {
if (m_hAccessor != NULL) {
DebugTrace((DWORD) this, "releasing accessor");
ReleaseAccessor();
}
DebugTrace((DWORD) this, "releasing rowset");
m_pRowset->Release();
m_pRowset = NULL;
}
TraceFunctLeave();
}