12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256 |
- /*
- * Copyright 2005 - 2016 Zarafa and its licensors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- #include <new>
- #include <kopano/platform.h>
- #include <kopano/lockhelper.hpp>
- /* Returns the rows for a contents- or hierarchytable
- *
- * objtype == MAPI_MESSAGE, then contents table
- * objtype == MAPI_MESSAGE, flags == MAPI_ASSOCIATED, then associated contents table
- * objtype == MAPI_FOLDER, then hierarchy table
- *
- * Tables are generated from SQL in the following way:
- *
- * Tables are constructed by joining the hierarchy table with the property table multiple
- * times, once for each requested property (column). Because each column of each row can always have
- * only one or zero records, a unique index is created on the property table, indexing (hierarchyid, type, tag).
- *
- * This means that for each cell that we request, the index needs to be accessed by the SQL
- * engine only once, which makes the actual query extremely fast.
- *
- * In tests, this has shown to required around 60ms for 30 rows and 10 columns from a table of 10000
- * rows. Also, this is a O(n log n) operation and therefore not prone to large scaling problems. (Yay!)
- * (with respect to the amount of columns, it is O(n), but that's quite constant, and so is the
- * actual amount of rows requested per query (also O(n)).
- *
- */
- #include "soapH.h"
- #include <kopano/kcodes.h>
- #include <mapidefs.h>
- #include <mapitags.h>
- #include <edkmdb.h>
- #include <kopano/mapiext.h>
- #include "kcore.hpp"
- #include "ECDatabaseUtils.h"
- #include <kopano/ECKeyTable.h>
- #include "ECGenProps.h"
- #include "ECStoreObjectTable.h"
- #include "ECStatsCollector.h"
- #include "ECSecurity.h"
- #include "ECIndexer.h"
- #include "ECSearchClient.h"
- #include "ECTPropsPurge.h"
- #include "SOAPUtils.h"
- #include <kopano/stringutil.h>
- #include <kopano/charset/utf8string.h>
- #include <kopano/charset/convert.h>
- #include <kopano/Trace.h>
- #include "ECSessionManager.h"
- #include "ECSession.h"
- #include <map>
- namespace KC {
- extern ECStatsCollector* g_lpStatsCollector;
- static bool IsTruncatableType(unsigned int ulTag)
- {
- switch(PROP_TYPE(ulTag)) {
- case PT_STRING8:
- case PT_UNICODE:
- case PT_BINARY:
- return true;
- default:
- return false;
- }
-
- return false;
- }
- static bool IsTruncated(const struct propVal *lpsPropVal)
- {
- if(!IsTruncatableType(lpsPropVal->ulPropTag))
- return false;
-
- switch(PROP_TYPE(lpsPropVal->ulPropTag)) {
- case PT_STRING8:
- case PT_UNICODE:
- return u8_len(lpsPropVal->Value.lpszA) == TABLE_CAP_STRING;
- case PT_BINARY:
- // previously we capped on 255 bytes, upgraded to 511.
- return lpsPropVal->Value.bin->__size == TABLE_CAP_STRING || lpsPropVal->Value.bin->__size == TABLE_CAP_BINARY;
- }
-
- return false;
- }
- ECStoreObjectTable::ECStoreObjectTable(ECSession *lpSession,
- unsigned int ulStoreId, GUID *lpGuid, unsigned int ulFolderId,
- unsigned int ulObjType, unsigned int ulFlags, unsigned int ulTableFlags,
- const ECLocale &locale) :
- ECGenericObjectTable(lpSession, ulObjType, ulFlags, locale)
- {
- auto lpODStore = new ECODStore;
- lpODStore->ulStoreId = ulStoreId;
- lpODStore->ulFolderId = ulFolderId;
- lpODStore->ulObjType = ulObjType;
- lpODStore->ulFlags = ulFlags;
- lpODStore->ulTableFlags = ulTableFlags;
- if(lpGuid) {
- lpODStore->lpGuid = new GUID;
- *lpODStore->lpGuid = *lpGuid;
- } else {
- lpODStore->lpGuid = NULL; // When NULL is specified, we get the store ID & guid for each separate row
- }
- // Set dataobject
- m_lpObjectData = lpODStore;
- // Set callback function for queryrowdata
- m_lpfnQueryRowData = QueryRowData;
- }
- ECStoreObjectTable::~ECStoreObjectTable()
- {
- if (m_lpObjectData == nullptr)
- return;
- auto lpODStore = static_cast<ECODStore *>(m_lpObjectData);
- delete lpODStore->lpGuid;
- delete lpODStore;
- }
- ECRESULT ECStoreObjectTable::Create(ECSession *lpSession, unsigned int ulStoreId, GUID *lpGuid, unsigned int ulFolderId, unsigned int ulObjType, unsigned int ulFlags, unsigned int ulTableFlags, const ECLocale &locale, ECStoreObjectTable **lppTable)
- {
- *lppTable = new(std::nothrow) ECStoreObjectTable(lpSession, ulStoreId,
- lpGuid, ulFolderId, ulObjType, ulFlags, ulTableFlags,
- locale);
- if (*lppTable == nullptr)
- return KCERR_NOT_ENOUGH_MEMORY;
- (*lppTable)->AddRef();
- return erSuccess;
- }
- ECRESULT ECStoreObjectTable::GetColumnsAll(ECListInt* lplstProps)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- ECDatabase* lpDatabase = NULL;
- auto lpODStore = static_cast<ECODStore *>(m_lpObjectData);
- ULONG ulPropID = 0;
- ulock_rec biglock(m_hLock);
- assert(lplstProps != NULL);
- er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- goto exit;
-
- //List always emtpy
- lplstProps->clear();
- if(!mapObjects.empty() && lpODStore->ulFolderId)
- {
- // Properties
- strQuery = "SELECT DISTINCT tproperties.tag, tproperties.type FROM tproperties WHERE folderid = " + stringify(lpODStore->ulFolderId);
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- goto exit;
-
- // Put the results into a STL list
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
- if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL)
- continue;
- ulPropID = atoi(lpDBRow[0]);
- lplstProps->push_back(PROP_TAG(atoi(lpDBRow[1]), ulPropID));
- }
- }// if(!mapObjects.empty())
- // Add some generated and standard properties
- lplstProps->push_back(PR_ENTRYID);
- lplstProps->push_back(PR_INSTANCE_KEY);
- lplstProps->push_back(PR_RECORD_KEY);
- lplstProps->push_back(PR_OBJECT_TYPE);
- lplstProps->push_back(PR_LAST_MODIFICATION_TIME);
- lplstProps->push_back(PR_CREATION_TIME);
- lplstProps->push_back(PR_PARENT_ENTRYID);
- lplstProps->push_back(PR_MAPPING_SIGNATURE);
- // Add properties only in the contents tables
- if(lpODStore->ulObjType == MAPI_MESSAGE && (lpODStore->ulFlags&MSGFLAG_ASSOCIATED) == 0)
- lplstProps->push_back(PR_MSG_STATUS);
-
- lplstProps->push_back(PR_ACCESS_LEVEL);
- //FIXME: only in folder or message table
- lplstProps->push_back(PR_ACCESS);
- exit:
- biglock.unlock();
- return er;
- }
- ECRESULT ECStoreObjectTable::ReloadTableMVData(ECObjectTableList* lplistRows, ECListInt* lplistMVPropTag)
- {
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- std::string strColName;
- ECDatabase* lpDatabase = NULL;
- int j;
- sObjectTableKey sRowItem;
- size_t cListRows = 0;
- ECRESULT er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- return er;
- assert(lplistMVPropTag->size() < 2); //FIXME: Limit of one 1 MV column
- // scan for MV-props and add rows
- strQuery = "SELECT h.id, orderid FROM hierarchy as h";
- j = 0;
- for (auto proptag : *lplistMVPropTag) {
- strColName = "col" + stringify(proptag);
- strQuery += " LEFT JOIN mvproperties as " + strColName + " ON h.id=" +
- strColName + ".hierarchyid AND " + strColName +
- ".tag=" + stringify(PROP_ID(proptag)) + " AND " +
- strColName + ".type=" +
- stringify(PROP_TYPE(NormalizeDBPropTag(proptag) & ~MV_INSTANCE));
- ++j;
- break; //FIXME: limit 1 column, multi MV cols
- } // for iterListMVPropTag
-
- strQuery += " WHERE h.id IN(";
- j = 0;
- cListRows = lplistRows->size();
- for (const auto &row : *lplistRows) {
- strQuery += stringify(row.ulObjId);
- if(j != (int)(cListRows-1))
- strQuery += ",";
- ++j;
- }
- strQuery += ") ORDER by id, orderid";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- while(1)
- {
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if(lpDBRow == NULL)
- break;
- sRowItem.ulObjId = atoui(lpDBRow[0]);
- sRowItem.ulOrderId = (lpDBRow[1] == NULL)? 0 : atoi(lpDBRow[1]);
- if(sRowItem.ulOrderId > 0)
- lplistRows->push_back(sRowItem);
- }
- return erSuccess;
- }
- // Interface to main row engine (bSubObjects is false)
- ECRESULT ECStoreObjectTable::QueryRowData(ECGenericObjectTable *lpThis, struct soap *soap, ECSession *lpSession, ECObjectTableList* lpRowList, struct propTagArray *lpsPropTagArray, void* lpObjectData, struct rowSet **lppRowSet, bool bCacheTableData, bool bTableLimit)
- {
- return ECStoreObjectTable::QueryRowData(lpThis, soap, lpSession, lpRowList, lpsPropTagArray, lpObjectData, lppRowSet, bCacheTableData, bTableLimit, false);
- }
- // Direct interface
- ECRESULT ECStoreObjectTable::QueryRowData(ECGenericObjectTable *lpThis, struct soap *soap, ECSession *lpSession, ECObjectTableList* lpRowList, struct propTagArray *lpsPropTagArray, void* lpObjectData, struct rowSet **lppRowSet, bool bCacheTableData, bool bTableLimit, bool bSubObjects)
- {
- ECRESULT er = erSuccess;
- gsoap_size_t i = 0, k = 0;
- unsigned int ulFolderId;
- unsigned int ulRowStoreId = 0;
- GUID sRowGuid;
- struct rowSet *lpsRowSet = NULL;
- auto lpODStore = static_cast<ECODStore *>(lpObjectData);
- ECDatabase *lpDatabase = NULL;
- std::map<unsigned int, std::map<sObjectTableKey, unsigned int> > mapStoreIdObjIds;
- std::map<sObjectTableKey, unsigned int> mapIncompleteRows;
- std::map<sObjectTableKey, unsigned int> mapRows;
- std::multimap<unsigned int, unsigned int> mapColumns;
- std::list<unsigned int> lstDeferred;
- std::set<unsigned int> setColumnIDs;
- ECObjectTableList lstRowOrder;
- sObjectTableKey sMapKey;
- std::map<sObjectTableKey, ECsObjects> mapObjects;
- ECListInt listMVSortCols;//Other mvprops then normal column set
- ECListInt listMVIColIds;
- string strCol;
- sObjectTableKey sKey;
-
- std::set<std::pair<unsigned int, unsigned int> > setCellDone;
- std::list<unsigned int> propList;
- assert(lpRowList != NULL);
- er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- goto exit;
- lpsRowSet = s_alloc<rowSet>(soap);
- lpsRowSet->__size = 0;
- lpsRowSet->__ptr = NULL;
- if (lpRowList == nullptr || lpRowList->empty()) {
- *lppRowSet = lpsRowSet;
- lpsRowSet = NULL;
- goto exit; // success
- }
- if (lpODStore->ulFlags & EC_TABLE_NOCAP)
- bTableLimit = false;
-
- // We return a square array with all the values
- lpsRowSet->__size = lpRowList->size();
- lpsRowSet->__ptr = s_alloc<propValArray>(soap, lpsRowSet->__size);
- memset(lpsRowSet->__ptr, 0, sizeof(propValArray) * lpsRowSet->__size);
- // Allocate memory for all rows
- for (i = 0; i < lpsRowSet->__size; ++i) {
- lpsRowSet->__ptr[i].__size = lpsPropTagArray->__size;
- lpsRowSet->__ptr[i].__ptr = s_alloc<propVal>(soap, lpsPropTagArray->__size);
- memset(lpsRowSet->__ptr[i].__ptr, 0, sizeof(propVal) * lpsPropTagArray->__size);
- }
- // Scan cache for anything that we can find, and generate any properties that don't come from normal database queries.
- i = 0;
- for (const auto &row : *lpRowList) {
- bool bRowComplete = true;
-
- for (k = 0; k < lpsPropTagArray->__size; ++k) {
- unsigned int ulPropTag;
-
- if (row.ulObjId == 0 || ECGenProps::GetPropSubstitute(lpODStore->ulObjType, lpsPropTagArray->__ptr[k], &ulPropTag) != erSuccess)
- ulPropTag = lpsPropTagArray->__ptr[k];
-
- // Get StoreId if needed
- if (lpODStore->lpGuid == NULL)
- // No store specified, so determine the store ID & guid from the object id
- lpSession->GetSessionManager()->GetCacheManager()->GetStore(row.ulObjId, &ulRowStoreId, &sRowGuid);
- else
- ulRowStoreId = lpODStore->ulStoreId;
- // Handle category header rows
- if (row.ulObjId == 0) {
- if (lpThis->GetPropCategory(soap, lpsPropTagArray->__ptr[k], row, &lpsRowSet->__ptr[i].__ptr[k]) != erSuccess) {
- // Other properties are not found
- lpsRowSet->__ptr[i].__ptr[k].__union = SOAP_UNION_propValData_ul;
- lpsRowSet->__ptr[i].__ptr[k].Value.ul = KCERR_NOT_FOUND;
- lpsRowSet->__ptr[i].__ptr[k].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(ulPropTag));
- }
- setCellDone.insert(std::make_pair(i,k));
- continue;
- }
- if (ECGenProps::IsPropComputedUncached(ulPropTag, lpODStore->ulObjType) == erSuccess) {
- if (ECGenProps::GetPropComputedUncached(soap, lpODStore, lpSession, ulPropTag, row.ulObjId, row.ulOrderId, ulRowStoreId, lpODStore->ulFolderId, lpODStore->ulObjType, &lpsRowSet->__ptr[i].__ptr[k]) != erSuccess)
- CopyEmptyCellToSOAPPropVal(soap, ulPropTag, &lpsRowSet->__ptr[i].__ptr[k]);
- setCellDone.insert(std::make_pair(i,k));
- continue;
- }
- // Handle PR_DEPTH
- if(ulPropTag == PR_DEPTH) {
- if (lpThis->GetComputedDepth(soap, lpSession, row.ulObjId, &lpsRowSet->__ptr[i].__ptr[k]) != erSuccess) {
- lpsRowSet->__ptr[i].__ptr[k].__union = SOAP_UNION_propValData_ul;
- lpsRowSet->__ptr[i].__ptr[k].ulPropTag = PR_DEPTH;
- lpsRowSet->__ptr[i].__ptr[k].Value.ul = 0;
- }
- setCellDone.insert(std::make_pair(i,k));
- continue;
- }
-
- if(ulPropTag == PR_PARENT_DISPLAY_A || ulPropTag == PR_PARENT_DISPLAY_W || ulPropTag == PR_EC_OUTGOING_FLAGS || ulPropTag == PR_EC_PARENT_HIERARCHYID || ulPropTag == PR_ASSOCIATED) {
- bRowComplete = false;
- continue; // These are not in cache, even if cache is complete for an item.
- }
-
- if(PROP_TYPE(ulPropTag) & MV_FLAG) {
- // Currently not caching MV values
- bRowComplete = false;
- continue;
- }
-
- // FIXME bComputed always false
- // FIXME optimisation possible to GetCell: much more efficient to get all cells in one row at once
- if (lpSession->GetSessionManager()->GetCacheManager()->GetCell(&row, ulPropTag, &lpsRowSet->__ptr[i].__ptr[k], soap, false) == erSuccess) {
- setCellDone.insert(std::make_pair(i,k));
- continue;
- }
- // None of the property generation tactics helped us, we need to get at least something from the database for this row.
- bRowComplete = false;
- }
-
- // Remember if we didn't have all cells in this row in the cache, and remember the row number
- if (!bRowComplete)
- mapIncompleteRows[row] = i;
- ++i;
- }
-
- if(!bSubObjects) {
- // Query from contents or hierarchy table, only do row-order processing for rows that have to be done via the row engine
-
- if(!mapIncompleteRows.empty()) {
- // Find rows that are in the deferred processing queue, and we have rows to be processed
- if (lpODStore->ulFolderId)
- er = GetDeferredTableUpdates(lpDatabase, lpODStore->ulFolderId, &lstDeferred);
- else
- er = GetDeferredTableUpdates(lpDatabase, lpRowList, &lstDeferred);
- if(er != erSuccess)
- goto exit;
-
- // Build list of rows that are incomplete (not in cache) AND deferred
- for (auto dfr : lstDeferred) {
- sKey.ulObjId = dfr;
- sKey.ulOrderId = 0;
- auto iterIncomplete = mapIncompleteRows.lower_bound(sKey);
- while (iterIncomplete != mapIncompleteRows.cend() &&
- iterIncomplete->first.ulObjId == dfr) {
- g_lpStatsCollector->Increment(SCN_DATABASE_DEFERRED_FETCHES);
- mapRows[iterIncomplete->first] = iterIncomplete->second;
- ++iterIncomplete;
- }
- }
- }
- } else {
- // Do row-order query for all incomplete rows
- mapRows = mapIncompleteRows;
- }
-
- if(!mapRows.empty()) {
- // Get rows from row order engine
- for (const auto &rowp : mapRows) {
- mapColumns.clear();
- // Find out which columns we need
- for (k = 0; k < lpsPropTagArray->__size; ++k) {
- if (setCellDone.count(std::make_pair(rowp.second, k)) != 0)
- continue;
- // Not done yet, remember that we need to get this column
- unsigned int ulPropTag;
- if (ECGenProps::GetPropSubstitute(lpODStore->ulObjType, lpsPropTagArray->__ptr[k], &ulPropTag) != erSuccess)
- ulPropTag = lpsPropTagArray->__ptr[k];
- mapColumns.insert(std::make_pair(ulPropTag, k));
- setCellDone.insert(std::make_pair(rowp.second, k)); // Done now
- }
-
- // Get actual data
- er = QueryRowDataByRow(lpThis, soap, lpSession, const_cast<sObjectTableKey &>(rowp.first), rowp.second, mapColumns, bTableLimit, lpsRowSet);
- if(er != erSuccess)
- goto exit;
- }
- }
- if(setCellDone.size() != (unsigned int)i*k) {
- // Some cells are not done yet, do them in column-order.
- mapStoreIdObjIds.clear();
- // Get parent info if needed
- if(lpODStore->ulFolderId == 0)
- lpSession->GetSessionManager()->GetCacheManager()->GetObjects(*lpRowList, mapObjects);
- // Split requests into same-folder blocks
- for (k = 0; k < lpsPropTagArray->__size; ++k) {
- i = 0;
- for (const auto &row : *lpRowList) {
- if (setCellDone.count(std::make_pair(i, k)) != 0) {
- ++i;
- continue; /* already done */
- }
-
- // Remember that there was some data int this column that we need to get
- setColumnIDs.insert(k);
-
- if(lpODStore->ulFolderId == 0) {
- auto iterObjects = mapObjects.find(row);
- if (iterObjects != mapObjects.cend())
- ulFolderId = iterObjects->second.ulParent;
- else
- /* This will cause the request to fail, since no items are in folder id 0. However, this is what we want since
- * the only thing we can do is return NOT_FOUND for each cell.*/
- ulFolderId = 0;
- } else {
- ulFolderId = lpODStore->ulFolderId;
- }
- mapStoreIdObjIds[ulFolderId][row] = i;
- setCellDone.insert(std::make_pair(i,k));
- ++i;
- }
-
- }
- // FIXME could fill setColumns per set of items instead of the whole table request
-
- mapColumns.clear();
- // Convert the set of column IDs to a map from property tag to column ID
- for (auto col_id : setColumnIDs) {
- unsigned int ulPropTag;
-
- if (ECGenProps::GetPropSubstitute(lpODStore->ulObjType, lpsPropTagArray->__ptr[col_id], &ulPropTag) != erSuccess)
- ulPropTag = lpsPropTagArray->__ptr[col_id];
- mapColumns.insert(std::make_pair(ulPropTag, col_id));
- }
- for (const auto &sio : mapStoreIdObjIds)
- er = QueryRowDataByColumn(lpThis, soap, lpSession, mapColumns, sio.first, sio.second, lpsRowSet);
- }
-
- if(!bTableLimit) {
- /* If no table limit was specified (so entire string requested, not just < 255 bytes), we have to do some more processing:
- *
- * - Check each column to see if it is truncatable at all (only string and binary columns are truncatable)
- * - Check each output value that we have already retrieved to see if it was truncated (value may have come from cache or column engine)
- * - Get any additional data via QueryRowDataByRow() if needed since that is the only method to get > 255 bytes
- */
- for (k = 0; k < lpsPropTagArray->__size; ++k) {
- if (!IsTruncatableType(lpsPropTagArray->__ptr[k]))
- continue;
- i = 0;
- for (const auto &row : *lpRowList) {
- if (!IsTruncated(&lpsRowSet->__ptr[i].__ptr[k])) {
- ++i;
- continue;
- }
- // We only want one column in this row
- mapColumns.clear();
- mapColumns.insert(std::make_pair(lpsPropTagArray->__ptr[k], k));
- // Un-truncate this value
- er = QueryRowDataByRow(lpThis, soap, lpSession, row, i, mapColumns, false, lpsRowSet);
- if (er != erSuccess)
- goto exit;
- ++i;
- }
- }
- }
-
- for (k = 0; k < lpsPropTagArray->__size; ++k) {
- // Do any post-processing operations
- if (ECGenProps::IsPropComputed(lpsPropTagArray->__ptr[k],
- lpODStore->ulObjType) != erSuccess)
- continue;
- i = 0;
- for (const auto &row : *lpRowList) {
- if (row.ulObjId == 0) {
- ++i;
- continue;
- }
- // Do not change category values!
- ECGenProps::GetPropComputed(soap, lpODStore->ulObjType,
- lpsPropTagArray->__ptr[k], row.ulObjId,
- &lpsRowSet->__ptr[i].__ptr[k]);
- ++i;
- }
- }
-
- *lppRowSet = lpsRowSet;
- lpsRowSet = NULL;
- exit:
- if (soap == NULL && lpsRowSet)
- FreeRowSet(lpsRowSet, true);
-
- return er;
- }
- ECRESULT ECStoreObjectTable::QueryRowDataByRow(ECGenericObjectTable *lpThis,
- struct soap *soap, ECSession *lpSession, const sObjectTableKey &sKey,
- unsigned int ulRowNum,
- std::multimap<unsigned int, unsigned int> &mapColumns, bool bTableLimit,
- struct rowSet *lpsRowSet)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- DB_LENGTHS lpDBLen = NULL;
- std::string strQuery;
- std::string strSubquery;
- ECDatabase *lpDatabase = NULL;
- bool bNeedProps = false;
- bool bNeedMVProps = false;
- bool bNeedMVIProps = false;
- bool bNeedSubQueries = false;
-
- std::string strSubQuery;
- std::string strCol;
- std::set<unsigned int> setSubQueries;
- // Select correct property column query according to whether we want to truncate or not
- std::string strPropColOrder = bTableLimit ? PROPCOLORDER_TRUNCATED : PROPCOLORDER;
- std::string strMVIPropColOrder = bTableLimit ? MVIPROPCOLORDER_TRUNCATED : MVIPROPCOLORDER;
-
- assert(lpsRowSet != NULL);
- er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- return er;
-
- g_lpStatsCollector->Increment(SCN_DATABASE_ROW_READS, 1);
- for (const auto &col : mapColumns) {
- unsigned int ulPropTag = col.first;
-
- // Remember the types of columns being requested for later use
- if(ECGenProps::GetPropSubquery(ulPropTag, strSubQuery) == erSuccess) {
- setSubQueries.insert(ulPropTag);
- bNeedSubQueries = true;
- } else if(ulPropTag & MV_FLAG) {
- if(ulPropTag & MV_INSTANCE)
- bNeedMVIProps = true;
- else
- bNeedMVProps = true;
- } else {
- bNeedProps = true;
- }
- }
-
- // Do normal properties
- if(bNeedProps) {
- // mapColumns now contains the data that we still need to request from the database for this row.
- strQuery = "SELECT " + strPropColOrder + " FROM properties WHERE hierarchyid="+stringify(sKey.ulObjId) + " AND properties.tag IN (";
-
- for (const auto &col : mapColumns) {
- // A subquery is defined for this type, we don't have to get the data in the normal way.
- if (setSubQueries.find(col.first) != setSubQueries.cend())
- continue;
- if ((col.first & MV_FLAG) == 0) {
- strQuery += stringify(PROP_ID(col.first));
- strQuery += ",";
- }
- }
-
- // Remove trailing ,
- strQuery.resize(strQuery.size()-1);
- strQuery += ")";
- }
-
- // Do MV properties
- if(bNeedMVProps) {
- if(!strQuery.empty())
- strQuery += " UNION ";
-
- strQuery += "SELECT " + (std::string)MVPROPCOLORDER + " FROM mvproperties WHERE hierarchyid="+stringify(sKey.ulObjId) + " AND mvproperties.tag IN (";
-
- for (const auto &col : mapColumns) {
- if (setSubQueries.find(col.first) != setSubQueries.cend())
- continue;
- if ((col.first & MV_FLAG) && !(col.first & MV_INSTANCE)) {
- strQuery += stringify(PROP_ID(col.first));
- strQuery += ",";
- }
- }
-
- strQuery.resize(strQuery.size()-1);
- strQuery += ")";
- strQuery += " GROUP BY hierarchyid, tag";
- }
-
- // Do MVI properties. Output from this part is handled exactly the same as normal properties
- if(bNeedMVIProps) {
- if(!strQuery.empty())
- strQuery += " UNION ";
-
- strQuery += "SELECT " + strMVIPropColOrder + " FROM mvproperties WHERE hierarchyid="+stringify(sKey.ulObjId);
-
- for (const auto &col : mapColumns) {
- if (setSubQueries.find(col.first) != setSubQueries.cend())
- continue;
- if ((col.first & MV_FLAG) && (col.first & MV_INSTANCE)) {
- strQuery += " AND (";
- strQuery += "orderid = " + stringify(sKey.ulOrderId) + " AND ";
- strQuery += "tag = " + stringify(PROP_ID(col.first));
- strQuery += ")";
- }
- }
- }
- // Do generated properties
- if(bNeedSubQueries) {
- std::string strPropColOrder;
-
- for (const auto &col : mapColumns) {
- if (ECGenProps::GetPropSubquery(col.first, strSubQuery) != erSuccess)
- continue;
- strPropColOrder = GetPropColOrder(col.first, strSubQuery);
- if (!strQuery.empty())
- strQuery += " UNION ";
- strQuery += " SELECT " + strPropColOrder +
- " FROM hierarchy WHERE hierarchy.id = " +
- stringify(sKey.ulObjId);
- }
- }
-
- if(!strQuery.empty()) {
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
-
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != 0) {
- if(lpDBRow[1] == NULL || lpDBRow[2] == NULL) {
- assert(false);
- continue;
- }
-
- lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
-
- unsigned int ulPropTag = PROP_TAG(atoui(lpDBRow[2]), atoui(lpDBRow[1]));
-
- // The same column may have been requested multiple times. If that is the case, SQL will give us one result for all columns. This
- // means we have to loop through all the same-property columns and add the same data everywhere.
- for (auto iterColumns = mapColumns.lower_bound(NormalizeDBPropTag(ulPropTag));
- iterColumns != mapColumns.cend() && CompareDBPropTag(iterColumns->first, ulPropTag); ) {
- // free prop if we're not allocing by soap
- if(soap == NULL && lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag != 0) {
- FreePropVal(&lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second], false);
- memset(&lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second], 0, sizeof(lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second]));
- }
- if(CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second]) != erSuccess) {
- // This can happen if a subquery returned a NULL field or if your database contains bad data (eg a NULL field where there shouldn't be)
- ++iterColumns;
- continue;
- }
-
- // Update property tag to requested property tag; requested type may have been PT_UNICODE while database contains PT_STRING8
- lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag = iterColumns->first;
- if ((lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag & MV_FLAG) == 0)
- // Cache value
- lpSession->GetSessionManager()->GetCacheManager()->SetCell(&sKey, iterColumns->first, &lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second]);
- else if ((lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag & MVI_FLAG) == MVI_FLAG)
- // Get rid of the MVI_FLAG
- lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag &= ~MVI_FLAG;
- // Remove from mapColumns so we know that we got a response from SQL
- mapColumns.erase(iterColumns++);
- }
- }
- }
- for (const auto &col : mapColumns) {
- assert(lpsRowSet->__ptr[ulRowNum].__ptr[col.second].ulPropTag == 0);
- CopyEmptyCellToSOAPPropVal(soap, col.first, &lpsRowSet->__ptr[ulRowNum].__ptr[col.second]);
- lpSession->GetSessionManager()->GetCacheManager()->SetCell(&sKey, col.first, &lpsRowSet->__ptr[ulRowNum].__ptr[col.second]);
- }
- return erSuccess;
- }
- /**
- * Read rows from tproperties table
- *
- * Since data in tproperties is truncated, this engine can only provide truncated property values.
- *
- *
- * @param[in] lpThis Pointer to main table object, optional (needed for categorization)
- * @param[in] soap Soap object to use for memory allocations, may be NULL for malloc() allocations
- * @param[in] lpSession Pointer to session for security context
- * @param[in] mapColumns Map of columns to retrieve with key = ulPropTag, value = column number
- * @param[in] ulFolderId Folder ID in which the rows exist
- * @param[in] mapObjIds Map of objects to retrieve with key = sObjectTableKey, value = row number
- * @param[out] lpsRowSet Row set where data will be written. Must be pre-allocated to hold all columns and rows requested
- */
- ECRESULT ECStoreObjectTable::QueryRowDataByColumn(ECGenericObjectTable *lpThis,
- struct soap *soap, ECSession *lpSession,
- const std::multimap<unsigned int, unsigned int> &mapColumns,
- unsigned int ulFolderId,
- const std::map<sObjectTableKey, unsigned int> &mapObjIds,
- struct rowSet *lpsRowSet)
- {
- ECRESULT er = erSuccess;
- std::string strQuery;
- std::string strHierarchyIds;
- std::string strTags, strMVTags, strMVITags;
- std::set<std::pair<unsigned int, unsigned int> > setDone;
- sObjectTableKey key;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- DB_LENGTHS lpDBLen = NULL;
- unsigned int ulTag = 0;
- unsigned int ulType = 0;
- ULONG ulMin, ulMax;
- ECDatabase *lpDatabase = NULL;
- std::set<unsigned int> setSubQueries;
- std::string strSubquery;
- std::string strPropColOrder;
- if (mapColumns.empty() || mapObjIds.empty())
- return erSuccess;
- er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- return er;
- ulMin = ulMax = PROP_ID(mapColumns.begin()->first);
- // Split columns into MV columns and non-MV columns
- for (const auto &col : mapColumns) {
- if ((col.first & MVI_FLAG) == MVI_FLAG) {
- if (!strMVITags.empty())
- strMVITags += ",";
- strMVITags += stringify(PROP_ID(col.first));
- }
- if ((col.first & MVI_FLAG) == MV_FLAG) {
- if (!strMVTags.empty())
- strMVTags += ",";
- strMVTags += stringify(PROP_ID(col.first));
- }
- if ((col.first & MVI_FLAG) == 0) {
- if (ECGenProps::GetPropSubquery(col.first, strSubquery) == erSuccess) {
- setSubQueries.insert(col.first);
- } else {
- if (!strTags.empty())
- strTags += ",";
- strTags += stringify(PROP_ID(col.first));
- }
- }
-
- ulMin = min(ulMin, PROP_ID(col.first));
- ulMax = max(ulMax, PROP_ID(col.first));
- }
-
- for (const auto &ob : mapObjIds) {
- if (!strHierarchyIds.empty())
- strHierarchyIds += ",";
- strHierarchyIds += stringify(ob.first.ulObjId);
- }
- // Get data
- if (!strTags.empty())
- strQuery = "SELECT " PROPCOLORDER ", hierarchyid, 0 FROM tproperties AS properties FORCE INDEX(PRIMARY) WHERE folderid=" + stringify(ulFolderId) + " AND hierarchyid IN(" + strHierarchyIds + ") AND tag IN (" + strTags +") AND tag >= " + stringify(ulMin) + " AND tag <= " + stringify(ulMax);
- if(!strMVTags.empty()) {
- if(!strQuery.empty())
- strQuery += " UNION ";
- strQuery += "SELECT " MVPROPCOLORDER ", hierarchyid, 0 FROM mvproperties WHERE hierarchyid IN(" + strHierarchyIds + ") AND tag IN (" + strMVTags +") GROUP BY hierarchyid, tag";
- }
- if(!strMVITags.empty()) {
- if(!strQuery.empty())
- strQuery += " UNION ";
- strQuery += "SELECT " MVIPROPCOLORDER ", hierarchyid, orderid FROM mvproperties WHERE hierarchyid IN(" + strHierarchyIds + ") AND tag IN (" +strMVITags +")";
- }
- if(!setSubQueries.empty()) {
- for (const auto &sq : setSubQueries) {
- if(!strQuery.empty())
- strQuery += " UNION ";
- if (ECGenProps::GetPropSubquery(sq, strSubquery) != erSuccess)
- continue;
- strPropColOrder = GetPropColOrder(sq, strSubquery);
- strQuery += " SELECT " + strPropColOrder + ", hierarchy.id, 0 FROM hierarchy WHERE hierarchy.id IN (" + strHierarchyIds + ")";
- }
- }
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if (er != erSuccess)
- return er;
-
- while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
- lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
-
- if(lpDBRow[FIELD_NR_MAX] == NULL || lpDBRow[FIELD_NR_MAX+1] == NULL || lpDBRow[FIELD_NR_TAG] == NULL || lpDBRow[FIELD_NR_TYPE] == NULL)
- continue; // No hierarchyid, tag or orderid (?)
-
- key.ulObjId = atoui(lpDBRow[FIELD_NR_MAX]);
- key.ulOrderId = atoui(lpDBRow[FIELD_NR_MAX+1]);
- ulTag = atoui(lpDBRow[FIELD_NR_TAG]);
- ulType = atoui(lpDBRow[FIELD_NR_TYPE]);
- // Find the right place to put things.
- // In an MVI column, we need to find the exact row to put the data in. We could get order id 0 from the database
- // while it was not requested. If that happens, then we should just discard the data.
- // In a non-MVI column, orderID from the DB is 0, and we should write that value into all rows with this object ID.
- // The lower_bound makes sure that if the requested row had order ID 1, we can still find it.
- std::map<sObjectTableKey, unsigned int>::const_iterator iterObjIds;
- if(ulType & MVI_FLAG)
- iterObjIds = mapObjIds.find(key);
- else {
- assert(key.ulOrderId == 0);
- iterObjIds = mapObjIds.lower_bound(key);
- }
-
- if (iterObjIds == mapObjIds.cend())
- continue; // Got data for a row we didn't request ? (Possible for MVI queries)
-
- while (iterObjIds != mapObjIds.cend() &&
- iterObjIds->first.ulObjId == key.ulObjId) {
- // WARNING. For PT_UNICODE columns, ulTag contains PT_STRING8, since that is the tag in the database. We rely
- // on PT_UNICODE = PT_STRING8 + 1 here since we do a lower_bound to scan for either PT_STRING8 or PT_UNICODE
- // and then use CompareDBPropTag to check the actual type in the while loop later. Same goes for PT_MV_UNICODE.
- auto iterColumns = mapColumns.lower_bound(PROP_TAG(ulType, ulTag));
- while (iterColumns != mapColumns.cend() &&
- CompareDBPropTag(iterColumns->first, PROP_TAG(ulType, ulTag))) {
- // free prop if we're not allocing by soap
- if(soap == NULL && lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag != 0) {
- FreePropVal(&lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second], false);
- memset(&lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second], 0, sizeof(lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]));
- }
- // Handle requesting the same tag multiple times; the data is returned only once, so we need to copy it to all the columns in which it was
- // requested. Note that requesting the same ROW more than once is not supported (it is a map, not a multimap)
- if (CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]) != erSuccess)
- CopyEmptyCellToSOAPPropVal(soap, iterColumns->first, &lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]);
-
- // Update propval to correct value. We have to do this because we may have requested PT_UNICODE while the database
- // contains PT_STRING8.
- if (PROP_TYPE(lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag) == PT_ERROR)
- lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(iterColumns->first));
- else
- lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag = iterColumns->first;
- if ((lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag & MV_FLAG) == 0)
- lpSession->GetSessionManager()->GetCacheManager()->SetCell((sObjectTableKey*)&iterObjIds->first, iterColumns->first, &lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]);
- else if ((lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag & MVI_FLAG) == MVI_FLAG)
- lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag &= ~MVI_FLAG;
-
- setDone.insert(std::make_pair(iterObjIds->second, iterColumns->second));
- ++iterColumns;
- }
-
- // We may have more than one row to fill in an MVI table; if we're handling a non-MVI property, then we have to duplicate that
- // value on each row
- if(ulType & MVI_FLAG)
- break; // For the MVI column, we should get one result for each row
- else
- ++iterObjIds; // For non-MVI columns, we get one result for all expanded rows
- }
- }
-
- for (const auto &col : mapColumns)
- for (const auto &ob : mapObjIds)
- if (setDone.count(std::make_pair(ob.second, col.second)) == 0) {
- // We may be overwriting a value that was retrieved from the cache before.
- if (soap == NULL && lpsRowSet->__ptr[ob.second].__ptr[col.second].ulPropTag != 0)
- FreePropVal(&lpsRowSet->__ptr[ob.second].__ptr[col.second], false);
- CopyEmptyCellToSOAPPropVal(soap, col.first, &lpsRowSet->__ptr[ob.second].__ptr[col.second]);
- lpSession->GetSessionManager()->GetCacheManager()->SetCell(const_cast<sObjectTableKey *>(&ob.first),
- col.first, &lpsRowSet->__ptr[ob.second].__ptr[col.second]);
- }
- return erSuccess;
- }
- ECRESULT ECStoreObjectTable::CopyEmptyCellToSOAPPropVal(struct soap *soap, unsigned int ulPropTag, struct propVal *lpPropVal)
- {
- ECRESULT er = erSuccess;
- lpPropVal->ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(ulPropTag));
- lpPropVal->__union = SOAP_UNION_propValData_ul;
- lpPropVal->Value.ul = KCERR_NOT_FOUND;
- return er;
- }
- ECRESULT ECStoreObjectTable::GetMVRowCount(unsigned int ulObjId, unsigned int *lpulCount)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpRow = NULL;
- std::string strQuery, strColName;
- int j;
- ECObjectTableList listRows;
- ECDatabase *lpDatabase = NULL;
- ulock_rec biglock(m_hLock);
- er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- goto exit;
- // scan for MV-props and add rows
- strQuery = "SELECT count(h.id) FROM hierarchy as h";
- j=0;
- for (auto tag : m_listMVSortCols) {
- strColName = "col" + stringify(tag);
- strQuery += " LEFT JOIN mvproperties as " + strColName +
- " ON h.id=" + strColName + ".hierarchyid AND " +
- strColName + ".tag=" +
- stringify(PROP_ID(tag)) + " AND " + strColName +
- ".type=" + stringify(PROP_TYPE(NormalizeDBPropTag(tag) &~MV_INSTANCE));
- ++j;
- }
-
- strQuery += " WHERE h.id="+stringify(ulObjId)+" ORDER by h.id, orderid";
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- goto exit;
-
- lpRow = lpDatabase->FetchRow(lpDBResult);
-
- if(lpRow == NULL || lpRow[0] == NULL) {
- er = KCERR_DATABASE_ERROR;
- ec_log_err("ECStoreObjectTable::GetMVRowCount(): row or column null");
- goto exit;
- }
-
- *lpulCount = atoi(lpRow[0]);
- exit:
- biglock.unlock();
- return er;
- }
- ECRESULT ECStoreObjectTable::Load()
- {
- ECRESULT er = erSuccess;
- ECDatabase *lpDatabase = NULL;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
- std::string strQuery;
- auto lpData = static_cast<ECODStore *>(m_lpObjectData);
- sObjectTableKey sRowItem;
-
- unsigned int ulFlags = lpData->ulFlags;
- unsigned int ulFolderId = lpData->ulFolderId;
- unsigned int ulObjType = lpData->ulObjType;
-
- unsigned int ulMaxItems = atoui(lpSession->GetSessionManager()->GetConfig()->GetSetting("folder_max_items"));
- unsigned int i;
-
- std::list<unsigned int> lstObjIds;
- er = lpSession->GetDatabase(&lpDatabase);
- if (er != erSuccess)
- return er;
- if (ulFolderId == 0)
- return erSuccess;
- // Clear old entries
- Clear();
- // Load the table with all the objects of type ulObjType and flags ulFlags in container ulParent
-
- strQuery = "SELECT hierarchy.id FROM hierarchy WHERE hierarchy.parent=" + stringify(ulFolderId);
- if(ulObjType == MAPI_MESSAGE)
- {
- strQuery += " AND hierarchy.type = " + stringify(ulObjType);
- if((ulFlags&MSGFLAG_DELETED) == 0)// Normal message and associated message
- strQuery += " AND hierarchy.flags & "+stringify(MSGFLAG_ASSOCIATED)+" = " + stringify(ulFlags&MSGFLAG_ASSOCIATED) + " AND hierarchy.flags & "+stringify(MSGFLAG_DELETED)+" = 0";
- else
- strQuery += " AND hierarchy.flags & "+stringify(MSGFLAG_ASSOCIATED)+" = " + stringify(ulFlags&MSGFLAG_ASSOCIATED) + " AND hierarchy.flags & "+stringify(MSGFLAG_DELETED)+" = " + stringify(MSGFLAG_DELETED);
-
- }
- else if(ulObjType == MAPI_FOLDER) {
- strQuery += " AND hierarchy.type = " + stringify(ulObjType);
- strQuery += " AND hierarchy.flags & "+stringify(MSGFLAG_DELETED)+" = " + stringify(ulFlags&MSGFLAG_DELETED);
- }else if(ulObjType == MAPI_MAILUSER) { //Read MAPI_MAILUSER and MAPI_DISTLIST
- strQuery += " AND (hierarchy.type = " + stringify(ulObjType) + " OR hierarchy.type = " + stringify(MAPI_DISTLIST) + ")";
- }else {
- strQuery += " AND hierarchy.type = " + stringify(ulObjType);
- }
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- i = 0;
- while(1) {
- lpDBRow = lpDatabase->FetchRow(lpDBResult);
- if(lpDBRow == NULL)
- break;
- if(lpDBRow[0] == NULL)
- continue;
-
- // Altough we don't want more than ulMaxItems entries, keep looping to get all the results from MySQL. We need to do this
- // because otherwise we can get out of sync with mysql which is still sending us results while we have already stopped
- // reading data.
- if(i > ulMaxItems)
- continue;
-
- lstObjIds.push_back(atoi(lpDBRow[0]));
- ++i;
- }
- LoadRows(&lstObjIds, 0);
- return erSuccess;
- }
- ECRESULT ECStoreObjectTable::CheckPermissions(unsigned int ulObjId)
- {
- ECRESULT er = erSuccess;
- unsigned int ulParent = 0;
- auto lpData = static_cast<ECODStore *>(m_lpObjectData);
- if (m_ulObjType == MAPI_FOLDER)
- return lpSession->GetSecurity()->CheckPermission(ulObjId, ecSecurityFolderVisible);
- if (m_ulObjType != MAPI_MESSAGE)
- return erSuccess;
- if (lpData->ulFolderId) {
- // We can either see all messages or none at all. Do this check once only as an optimisation.
- if (!this->fPermissionRead) {
- this->ulPermission = lpSession->GetSecurity()->CheckPermission(lpData->ulFolderId, ecSecurityRead);
- this->fPermissionRead = true;
- }
- return this->ulPermission;
- }
- // Get the parent id of the row we're inserting (this is very probably cached from Load())
- er = lpSession->GetSessionManager()->GetCacheManager()->GetParent(ulObjId, &ulParent);
- if (er != erSuccess)
- return er;
- // This will be cached after the first row in the table is added
- return lpSession->GetSecurity()->CheckPermission(ulParent, ecSecurityRead);
- }
- ECRESULT ECStoreObjectTable::AddRowKey(ECObjectTableList* lpRows, unsigned int *lpulLoaded, unsigned int ulFlags, bool bLoad, bool bOverride, struct restrictTable *lpOverride)
- {
- ECRESULT er = erSuccess;
- GUID guidServer;
- auto lpODStore = static_cast<ECODStore *>(m_lpObjectData);
- std::list<unsigned int> lstIndexerResults;
- std::list<unsigned int> lstFolders;
- std::set<unsigned int> setMatches;
- ECObjectTableList sMatchedRows;
- ECDatabase *lpDatabase = NULL;
- struct restrictTable *lpNewRestrict = NULL;
- std::string suggestion;
-
- assert(!bOverride); // Default implementation never has override enabled, so we should never see this
- assert(lpOverride == NULL);
- ulock_rec biglock(m_hLock);
-
- // Use normal table update code if:
- // - not an initial load (but a table update)
- // - no restriction
- // - not a restriction on a folder (eg searchfolder)
- if(!bLoad || !lpsRestrict || !lpODStore->ulFolderId || !lpODStore->ulStoreId || (lpODStore->ulFlags & MAPI_ASSOCIATED)) {
- er = ECGenericObjectTable::AddRowKey(lpRows, lpulLoaded, ulFlags, bLoad, false, NULL);
- } else {
- // Attempt to use the indexer
- er = lpSession->GetSessionManager()->GetServerGUID(&guidServer);
- if(er != erSuccess)
- goto exit;
-
- lstFolders.push_back(lpODStore->ulFolderId);
-
- er = lpSession->GetDatabase(&lpDatabase);
- if(er != erSuccess)
- goto exit;
- if (GetIndexerResults(lpDatabase, lpSession->GetSessionManager()->GetConfig(), lpSession->GetSessionManager()->GetCacheManager(), &guidServer, lpODStore->lpGuid, lstFolders, lpsRestrict, &lpNewRestrict, lstIndexerResults, suggestion) != erSuccess) {
- // Cannot handle this restriction with the indexer, use 'normal' restriction code
- // Reasons can be:
- // - restriction too complex
- // - folder not indexed
- // - indexer not running / not configured
- er = ECGenericObjectTable::AddRowKey(lpRows, lpulLoaded, ulFlags, bLoad, false, NULL);
- goto exit;
- }
-
- // Put the results in setMatches
- std::copy(lstIndexerResults.begin(), lstIndexerResults.end(), std::inserter(setMatches, setMatches.begin()));
-
- /* Filter the incoming row set with the matches.
- *
- * Actually, we should not have to do this. We could just take the result set from the indexer and
- * feed that directly to AddRowKey. However, the indexer may be slightly 'behind' in time, and the
- * list of objects in lpRows is more up-to-date. To make sure that we will not generate 'phantom' rows
- * of deleted items we take the cross section of lpRows and setMatches. In most cases the result
- * will be equal to setMatches though.
- */
- for (const auto &row : *lpRows)
- if (setMatches.find(row.ulObjId) != setMatches.end())
- sMatchedRows.push_back(row);
-
- // Pass filtered results to AddRowKey, which will perform any further filtering required
- er = ECGenericObjectTable::AddRowKey(&sMatchedRows, lpulLoaded, ulFlags, bLoad, true, lpNewRestrict);
- if(er != erSuccess)
- goto exit;
- }
-
- exit:
- biglock.unlock();
- if(lpNewRestrict)
- FreeRestrictTable(lpNewRestrict);
- return er;
-
- }
- /**
- * Get all deferred changes for a specific folder
- */
- ECRESULT GetDeferredTableUpdates(ECDatabase *lpDatabase, unsigned int ulFolderId, std::list<unsigned int> *lpDeferred)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
-
- lpDeferred->clear();
-
- std::string strQuery = "SELECT hierarchyid FROM deferredupdate WHERE folderid = " + stringify(ulFolderId);
-
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- while ((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL)
- lpDeferred->push_back(atoui(lpDBRow[0]));
- return erSuccess;
- }
- /**
- * Get all deferred changes for a specific folder
- */
- ECRESULT GetDeferredTableUpdates(ECDatabase *lpDatabase, ECObjectTableList* lpRowList, std::list<unsigned int> *lpDeferred)
- {
- ECRESULT er = erSuccess;
- DB_RESULT lpDBResult;
- DB_ROW lpDBRow = NULL;
-
- lpDeferred->clear();
-
- std::string strQuery = "SELECT hierarchyid FROM deferredupdate WHERE hierarchyid IN( ";
- for (const auto &row : *lpRowList) {
- strQuery += stringify(row.ulObjId);
- strQuery += ",";
- }
-
- // Remove trailing comma
- strQuery.resize(strQuery.size()-1);
- strQuery += ")";
-
- er = lpDatabase->DoSelect(strQuery, &lpDBResult);
- if(er != erSuccess)
- return er;
- while ((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL)
- lpDeferred->push_back(atoui(lpDBRow[0]));
- return erSuccess;
- }
- } /* namespace */
|