ECStoreObjectTable.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256
  1. /*
  2. * Copyright 2005 - 2016 Zarafa and its licensors
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3,
  6. * as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. *
  16. */
  17. #include <new>
  18. #include <kopano/platform.h>
  19. #include <kopano/lockhelper.hpp>
  20. /* Returns the rows for a contents- or hierarchytable
  21. *
  22. * objtype == MAPI_MESSAGE, then contents table
  23. * objtype == MAPI_MESSAGE, flags == MAPI_ASSOCIATED, then associated contents table
  24. * objtype == MAPI_FOLDER, then hierarchy table
  25. *
  26. * Tables are generated from SQL in the following way:
  27. *
  28. * Tables are constructed by joining the hierarchy table with the property table multiple
  29. * times, once for each requested property (column). Because each column of each row can always have
  30. * only one or zero records, a unique index is created on the property table, indexing (hierarchyid, type, tag).
  31. *
  32. * This means that for each cell that we request, the index needs to be accessed by the SQL
  33. * engine only once, which makes the actual query extremely fast.
  34. *
  35. * In tests, this has shown to required around 60ms for 30 rows and 10 columns from a table of 10000
  36. * rows. Also, this is a O(n log n) operation and therefore not prone to large scaling problems. (Yay!)
  37. * (with respect to the amount of columns, it is O(n), but that's quite constant, and so is the
  38. * actual amount of rows requested per query (also O(n)).
  39. *
  40. */
  41. #include "soapH.h"
  42. #include <kopano/kcodes.h>
  43. #include <mapidefs.h>
  44. #include <mapitags.h>
  45. #include <edkmdb.h>
  46. #include <kopano/mapiext.h>
  47. #include "kcore.hpp"
  48. #include "ECDatabaseUtils.h"
  49. #include <kopano/ECKeyTable.h>
  50. #include "ECGenProps.h"
  51. #include "ECStoreObjectTable.h"
  52. #include "ECStatsCollector.h"
  53. #include "ECSecurity.h"
  54. #include "ECIndexer.h"
  55. #include "ECSearchClient.h"
  56. #include "ECTPropsPurge.h"
  57. #include "SOAPUtils.h"
  58. #include <kopano/stringutil.h>
  59. #include <kopano/charset/utf8string.h>
  60. #include <kopano/charset/convert.h>
  61. #include <kopano/Trace.h>
  62. #include "ECSessionManager.h"
  63. #include "ECSession.h"
  64. #include <map>
  65. namespace KC {
  66. extern ECStatsCollector* g_lpStatsCollector;
  67. static bool IsTruncatableType(unsigned int ulTag)
  68. {
  69. switch(PROP_TYPE(ulTag)) {
  70. case PT_STRING8:
  71. case PT_UNICODE:
  72. case PT_BINARY:
  73. return true;
  74. default:
  75. return false;
  76. }
  77. return false;
  78. }
  79. static bool IsTruncated(const struct propVal *lpsPropVal)
  80. {
  81. if(!IsTruncatableType(lpsPropVal->ulPropTag))
  82. return false;
  83. switch(PROP_TYPE(lpsPropVal->ulPropTag)) {
  84. case PT_STRING8:
  85. case PT_UNICODE:
  86. return u8_len(lpsPropVal->Value.lpszA) == TABLE_CAP_STRING;
  87. case PT_BINARY:
  88. // previously we capped on 255 bytes, upgraded to 511.
  89. return lpsPropVal->Value.bin->__size == TABLE_CAP_STRING || lpsPropVal->Value.bin->__size == TABLE_CAP_BINARY;
  90. }
  91. return false;
  92. }
  93. ECStoreObjectTable::ECStoreObjectTable(ECSession *lpSession,
  94. unsigned int ulStoreId, GUID *lpGuid, unsigned int ulFolderId,
  95. unsigned int ulObjType, unsigned int ulFlags, unsigned int ulTableFlags,
  96. const ECLocale &locale) :
  97. ECGenericObjectTable(lpSession, ulObjType, ulFlags, locale)
  98. {
  99. auto lpODStore = new ECODStore;
  100. lpODStore->ulStoreId = ulStoreId;
  101. lpODStore->ulFolderId = ulFolderId;
  102. lpODStore->ulObjType = ulObjType;
  103. lpODStore->ulFlags = ulFlags;
  104. lpODStore->ulTableFlags = ulTableFlags;
  105. if(lpGuid) {
  106. lpODStore->lpGuid = new GUID;
  107. *lpODStore->lpGuid = *lpGuid;
  108. } else {
  109. lpODStore->lpGuid = NULL; // When NULL is specified, we get the store ID & guid for each separate row
  110. }
  111. // Set dataobject
  112. m_lpObjectData = lpODStore;
  113. // Set callback function for queryrowdata
  114. m_lpfnQueryRowData = QueryRowData;
  115. }
  116. ECStoreObjectTable::~ECStoreObjectTable()
  117. {
  118. if (m_lpObjectData == nullptr)
  119. return;
  120. auto lpODStore = static_cast<ECODStore *>(m_lpObjectData);
  121. delete lpODStore->lpGuid;
  122. delete lpODStore;
  123. }
  124. 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)
  125. {
  126. *lppTable = new(std::nothrow) ECStoreObjectTable(lpSession, ulStoreId,
  127. lpGuid, ulFolderId, ulObjType, ulFlags, ulTableFlags,
  128. locale);
  129. if (*lppTable == nullptr)
  130. return KCERR_NOT_ENOUGH_MEMORY;
  131. (*lppTable)->AddRef();
  132. return erSuccess;
  133. }
  134. ECRESULT ECStoreObjectTable::GetColumnsAll(ECListInt* lplstProps)
  135. {
  136. ECRESULT er = erSuccess;
  137. DB_RESULT lpDBResult;
  138. DB_ROW lpDBRow = NULL;
  139. std::string strQuery;
  140. ECDatabase* lpDatabase = NULL;
  141. auto lpODStore = static_cast<ECODStore *>(m_lpObjectData);
  142. ULONG ulPropID = 0;
  143. ulock_rec biglock(m_hLock);
  144. assert(lplstProps != NULL);
  145. er = lpSession->GetDatabase(&lpDatabase);
  146. if (er != erSuccess)
  147. goto exit;
  148. //List always emtpy
  149. lplstProps->clear();
  150. if(!mapObjects.empty() && lpODStore->ulFolderId)
  151. {
  152. // Properties
  153. strQuery = "SELECT DISTINCT tproperties.tag, tproperties.type FROM tproperties WHERE folderid = " + stringify(lpODStore->ulFolderId);
  154. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  155. if(er != erSuccess)
  156. goto exit;
  157. // Put the results into a STL list
  158. while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
  159. if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL)
  160. continue;
  161. ulPropID = atoi(lpDBRow[0]);
  162. lplstProps->push_back(PROP_TAG(atoi(lpDBRow[1]), ulPropID));
  163. }
  164. }// if(!mapObjects.empty())
  165. // Add some generated and standard properties
  166. lplstProps->push_back(PR_ENTRYID);
  167. lplstProps->push_back(PR_INSTANCE_KEY);
  168. lplstProps->push_back(PR_RECORD_KEY);
  169. lplstProps->push_back(PR_OBJECT_TYPE);
  170. lplstProps->push_back(PR_LAST_MODIFICATION_TIME);
  171. lplstProps->push_back(PR_CREATION_TIME);
  172. lplstProps->push_back(PR_PARENT_ENTRYID);
  173. lplstProps->push_back(PR_MAPPING_SIGNATURE);
  174. // Add properties only in the contents tables
  175. if(lpODStore->ulObjType == MAPI_MESSAGE && (lpODStore->ulFlags&MSGFLAG_ASSOCIATED) == 0)
  176. lplstProps->push_back(PR_MSG_STATUS);
  177. lplstProps->push_back(PR_ACCESS_LEVEL);
  178. //FIXME: only in folder or message table
  179. lplstProps->push_back(PR_ACCESS);
  180. exit:
  181. biglock.unlock();
  182. return er;
  183. }
  184. ECRESULT ECStoreObjectTable::ReloadTableMVData(ECObjectTableList* lplistRows, ECListInt* lplistMVPropTag)
  185. {
  186. DB_RESULT lpDBResult;
  187. DB_ROW lpDBRow = NULL;
  188. std::string strQuery;
  189. std::string strColName;
  190. ECDatabase* lpDatabase = NULL;
  191. int j;
  192. sObjectTableKey sRowItem;
  193. size_t cListRows = 0;
  194. ECRESULT er = lpSession->GetDatabase(&lpDatabase);
  195. if (er != erSuccess)
  196. return er;
  197. assert(lplistMVPropTag->size() < 2); //FIXME: Limit of one 1 MV column
  198. // scan for MV-props and add rows
  199. strQuery = "SELECT h.id, orderid FROM hierarchy as h";
  200. j = 0;
  201. for (auto proptag : *lplistMVPropTag) {
  202. strColName = "col" + stringify(proptag);
  203. strQuery += " LEFT JOIN mvproperties as " + strColName + " ON h.id=" +
  204. strColName + ".hierarchyid AND " + strColName +
  205. ".tag=" + stringify(PROP_ID(proptag)) + " AND " +
  206. strColName + ".type=" +
  207. stringify(PROP_TYPE(NormalizeDBPropTag(proptag) & ~MV_INSTANCE));
  208. ++j;
  209. break; //FIXME: limit 1 column, multi MV cols
  210. } // for iterListMVPropTag
  211. strQuery += " WHERE h.id IN(";
  212. j = 0;
  213. cListRows = lplistRows->size();
  214. for (const auto &row : *lplistRows) {
  215. strQuery += stringify(row.ulObjId);
  216. if(j != (int)(cListRows-1))
  217. strQuery += ",";
  218. ++j;
  219. }
  220. strQuery += ") ORDER by id, orderid";
  221. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  222. if(er != erSuccess)
  223. return er;
  224. while(1)
  225. {
  226. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  227. if(lpDBRow == NULL)
  228. break;
  229. sRowItem.ulObjId = atoui(lpDBRow[0]);
  230. sRowItem.ulOrderId = (lpDBRow[1] == NULL)? 0 : atoi(lpDBRow[1]);
  231. if(sRowItem.ulOrderId > 0)
  232. lplistRows->push_back(sRowItem);
  233. }
  234. return erSuccess;
  235. }
  236. // Interface to main row engine (bSubObjects is false)
  237. ECRESULT ECStoreObjectTable::QueryRowData(ECGenericObjectTable *lpThis, struct soap *soap, ECSession *lpSession, ECObjectTableList* lpRowList, struct propTagArray *lpsPropTagArray, void* lpObjectData, struct rowSet **lppRowSet, bool bCacheTableData, bool bTableLimit)
  238. {
  239. return ECStoreObjectTable::QueryRowData(lpThis, soap, lpSession, lpRowList, lpsPropTagArray, lpObjectData, lppRowSet, bCacheTableData, bTableLimit, false);
  240. }
  241. // Direct interface
  242. 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)
  243. {
  244. ECRESULT er = erSuccess;
  245. gsoap_size_t i = 0, k = 0;
  246. unsigned int ulFolderId;
  247. unsigned int ulRowStoreId = 0;
  248. GUID sRowGuid;
  249. struct rowSet *lpsRowSet = NULL;
  250. auto lpODStore = static_cast<ECODStore *>(lpObjectData);
  251. ECDatabase *lpDatabase = NULL;
  252. std::map<unsigned int, std::map<sObjectTableKey, unsigned int> > mapStoreIdObjIds;
  253. std::map<sObjectTableKey, unsigned int> mapIncompleteRows;
  254. std::map<sObjectTableKey, unsigned int> mapRows;
  255. std::multimap<unsigned int, unsigned int> mapColumns;
  256. std::list<unsigned int> lstDeferred;
  257. std::set<unsigned int> setColumnIDs;
  258. ECObjectTableList lstRowOrder;
  259. sObjectTableKey sMapKey;
  260. std::map<sObjectTableKey, ECsObjects> mapObjects;
  261. ECListInt listMVSortCols;//Other mvprops then normal column set
  262. ECListInt listMVIColIds;
  263. string strCol;
  264. sObjectTableKey sKey;
  265. std::set<std::pair<unsigned int, unsigned int> > setCellDone;
  266. std::list<unsigned int> propList;
  267. assert(lpRowList != NULL);
  268. er = lpSession->GetDatabase(&lpDatabase);
  269. if (er != erSuccess)
  270. goto exit;
  271. lpsRowSet = s_alloc<rowSet>(soap);
  272. lpsRowSet->__size = 0;
  273. lpsRowSet->__ptr = NULL;
  274. if (lpRowList == nullptr || lpRowList->empty()) {
  275. *lppRowSet = lpsRowSet;
  276. lpsRowSet = NULL;
  277. goto exit; // success
  278. }
  279. if (lpODStore->ulFlags & EC_TABLE_NOCAP)
  280. bTableLimit = false;
  281. // We return a square array with all the values
  282. lpsRowSet->__size = lpRowList->size();
  283. lpsRowSet->__ptr = s_alloc<propValArray>(soap, lpsRowSet->__size);
  284. memset(lpsRowSet->__ptr, 0, sizeof(propValArray) * lpsRowSet->__size);
  285. // Allocate memory for all rows
  286. for (i = 0; i < lpsRowSet->__size; ++i) {
  287. lpsRowSet->__ptr[i].__size = lpsPropTagArray->__size;
  288. lpsRowSet->__ptr[i].__ptr = s_alloc<propVal>(soap, lpsPropTagArray->__size);
  289. memset(lpsRowSet->__ptr[i].__ptr, 0, sizeof(propVal) * lpsPropTagArray->__size);
  290. }
  291. // Scan cache for anything that we can find, and generate any properties that don't come from normal database queries.
  292. i = 0;
  293. for (const auto &row : *lpRowList) {
  294. bool bRowComplete = true;
  295. for (k = 0; k < lpsPropTagArray->__size; ++k) {
  296. unsigned int ulPropTag;
  297. if (row.ulObjId == 0 || ECGenProps::GetPropSubstitute(lpODStore->ulObjType, lpsPropTagArray->__ptr[k], &ulPropTag) != erSuccess)
  298. ulPropTag = lpsPropTagArray->__ptr[k];
  299. // Get StoreId if needed
  300. if (lpODStore->lpGuid == NULL)
  301. // No store specified, so determine the store ID & guid from the object id
  302. lpSession->GetSessionManager()->GetCacheManager()->GetStore(row.ulObjId, &ulRowStoreId, &sRowGuid);
  303. else
  304. ulRowStoreId = lpODStore->ulStoreId;
  305. // Handle category header rows
  306. if (row.ulObjId == 0) {
  307. if (lpThis->GetPropCategory(soap, lpsPropTagArray->__ptr[k], row, &lpsRowSet->__ptr[i].__ptr[k]) != erSuccess) {
  308. // Other properties are not found
  309. lpsRowSet->__ptr[i].__ptr[k].__union = SOAP_UNION_propValData_ul;
  310. lpsRowSet->__ptr[i].__ptr[k].Value.ul = KCERR_NOT_FOUND;
  311. lpsRowSet->__ptr[i].__ptr[k].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(ulPropTag));
  312. }
  313. setCellDone.insert(std::make_pair(i,k));
  314. continue;
  315. }
  316. if (ECGenProps::IsPropComputedUncached(ulPropTag, lpODStore->ulObjType) == erSuccess) {
  317. if (ECGenProps::GetPropComputedUncached(soap, lpODStore, lpSession, ulPropTag, row.ulObjId, row.ulOrderId, ulRowStoreId, lpODStore->ulFolderId, lpODStore->ulObjType, &lpsRowSet->__ptr[i].__ptr[k]) != erSuccess)
  318. CopyEmptyCellToSOAPPropVal(soap, ulPropTag, &lpsRowSet->__ptr[i].__ptr[k]);
  319. setCellDone.insert(std::make_pair(i,k));
  320. continue;
  321. }
  322. // Handle PR_DEPTH
  323. if(ulPropTag == PR_DEPTH) {
  324. if (lpThis->GetComputedDepth(soap, lpSession, row.ulObjId, &lpsRowSet->__ptr[i].__ptr[k]) != erSuccess) {
  325. lpsRowSet->__ptr[i].__ptr[k].__union = SOAP_UNION_propValData_ul;
  326. lpsRowSet->__ptr[i].__ptr[k].ulPropTag = PR_DEPTH;
  327. lpsRowSet->__ptr[i].__ptr[k].Value.ul = 0;
  328. }
  329. setCellDone.insert(std::make_pair(i,k));
  330. continue;
  331. }
  332. if(ulPropTag == PR_PARENT_DISPLAY_A || ulPropTag == PR_PARENT_DISPLAY_W || ulPropTag == PR_EC_OUTGOING_FLAGS || ulPropTag == PR_EC_PARENT_HIERARCHYID || ulPropTag == PR_ASSOCIATED) {
  333. bRowComplete = false;
  334. continue; // These are not in cache, even if cache is complete for an item.
  335. }
  336. if(PROP_TYPE(ulPropTag) & MV_FLAG) {
  337. // Currently not caching MV values
  338. bRowComplete = false;
  339. continue;
  340. }
  341. // FIXME bComputed always false
  342. // FIXME optimisation possible to GetCell: much more efficient to get all cells in one row at once
  343. if (lpSession->GetSessionManager()->GetCacheManager()->GetCell(&row, ulPropTag, &lpsRowSet->__ptr[i].__ptr[k], soap, false) == erSuccess) {
  344. setCellDone.insert(std::make_pair(i,k));
  345. continue;
  346. }
  347. // None of the property generation tactics helped us, we need to get at least something from the database for this row.
  348. bRowComplete = false;
  349. }
  350. // Remember if we didn't have all cells in this row in the cache, and remember the row number
  351. if (!bRowComplete)
  352. mapIncompleteRows[row] = i;
  353. ++i;
  354. }
  355. if(!bSubObjects) {
  356. // Query from contents or hierarchy table, only do row-order processing for rows that have to be done via the row engine
  357. if(!mapIncompleteRows.empty()) {
  358. // Find rows that are in the deferred processing queue, and we have rows to be processed
  359. if (lpODStore->ulFolderId)
  360. er = GetDeferredTableUpdates(lpDatabase, lpODStore->ulFolderId, &lstDeferred);
  361. else
  362. er = GetDeferredTableUpdates(lpDatabase, lpRowList, &lstDeferred);
  363. if(er != erSuccess)
  364. goto exit;
  365. // Build list of rows that are incomplete (not in cache) AND deferred
  366. for (auto dfr : lstDeferred) {
  367. sKey.ulObjId = dfr;
  368. sKey.ulOrderId = 0;
  369. auto iterIncomplete = mapIncompleteRows.lower_bound(sKey);
  370. while (iterIncomplete != mapIncompleteRows.cend() &&
  371. iterIncomplete->first.ulObjId == dfr) {
  372. g_lpStatsCollector->Increment(SCN_DATABASE_DEFERRED_FETCHES);
  373. mapRows[iterIncomplete->first] = iterIncomplete->second;
  374. ++iterIncomplete;
  375. }
  376. }
  377. }
  378. } else {
  379. // Do row-order query for all incomplete rows
  380. mapRows = mapIncompleteRows;
  381. }
  382. if(!mapRows.empty()) {
  383. // Get rows from row order engine
  384. for (const auto &rowp : mapRows) {
  385. mapColumns.clear();
  386. // Find out which columns we need
  387. for (k = 0; k < lpsPropTagArray->__size; ++k) {
  388. if (setCellDone.count(std::make_pair(rowp.second, k)) != 0)
  389. continue;
  390. // Not done yet, remember that we need to get this column
  391. unsigned int ulPropTag;
  392. if (ECGenProps::GetPropSubstitute(lpODStore->ulObjType, lpsPropTagArray->__ptr[k], &ulPropTag) != erSuccess)
  393. ulPropTag = lpsPropTagArray->__ptr[k];
  394. mapColumns.insert(std::make_pair(ulPropTag, k));
  395. setCellDone.insert(std::make_pair(rowp.second, k)); // Done now
  396. }
  397. // Get actual data
  398. er = QueryRowDataByRow(lpThis, soap, lpSession, const_cast<sObjectTableKey &>(rowp.first), rowp.second, mapColumns, bTableLimit, lpsRowSet);
  399. if(er != erSuccess)
  400. goto exit;
  401. }
  402. }
  403. if(setCellDone.size() != (unsigned int)i*k) {
  404. // Some cells are not done yet, do them in column-order.
  405. mapStoreIdObjIds.clear();
  406. // Get parent info if needed
  407. if(lpODStore->ulFolderId == 0)
  408. lpSession->GetSessionManager()->GetCacheManager()->GetObjects(*lpRowList, mapObjects);
  409. // Split requests into same-folder blocks
  410. for (k = 0; k < lpsPropTagArray->__size; ++k) {
  411. i = 0;
  412. for (const auto &row : *lpRowList) {
  413. if (setCellDone.count(std::make_pair(i, k)) != 0) {
  414. ++i;
  415. continue; /* already done */
  416. }
  417. // Remember that there was some data int this column that we need to get
  418. setColumnIDs.insert(k);
  419. if(lpODStore->ulFolderId == 0) {
  420. auto iterObjects = mapObjects.find(row);
  421. if (iterObjects != mapObjects.cend())
  422. ulFolderId = iterObjects->second.ulParent;
  423. else
  424. /* This will cause the request to fail, since no items are in folder id 0. However, this is what we want since
  425. * the only thing we can do is return NOT_FOUND for each cell.*/
  426. ulFolderId = 0;
  427. } else {
  428. ulFolderId = lpODStore->ulFolderId;
  429. }
  430. mapStoreIdObjIds[ulFolderId][row] = i;
  431. setCellDone.insert(std::make_pair(i,k));
  432. ++i;
  433. }
  434. }
  435. // FIXME could fill setColumns per set of items instead of the whole table request
  436. mapColumns.clear();
  437. // Convert the set of column IDs to a map from property tag to column ID
  438. for (auto col_id : setColumnIDs) {
  439. unsigned int ulPropTag;
  440. if (ECGenProps::GetPropSubstitute(lpODStore->ulObjType, lpsPropTagArray->__ptr[col_id], &ulPropTag) != erSuccess)
  441. ulPropTag = lpsPropTagArray->__ptr[col_id];
  442. mapColumns.insert(std::make_pair(ulPropTag, col_id));
  443. }
  444. for (const auto &sio : mapStoreIdObjIds)
  445. er = QueryRowDataByColumn(lpThis, soap, lpSession, mapColumns, sio.first, sio.second, lpsRowSet);
  446. }
  447. if(!bTableLimit) {
  448. /* If no table limit was specified (so entire string requested, not just < 255 bytes), we have to do some more processing:
  449. *
  450. * - Check each column to see if it is truncatable at all (only string and binary columns are truncatable)
  451. * - Check each output value that we have already retrieved to see if it was truncated (value may have come from cache or column engine)
  452. * - Get any additional data via QueryRowDataByRow() if needed since that is the only method to get > 255 bytes
  453. */
  454. for (k = 0; k < lpsPropTagArray->__size; ++k) {
  455. if (!IsTruncatableType(lpsPropTagArray->__ptr[k]))
  456. continue;
  457. i = 0;
  458. for (const auto &row : *lpRowList) {
  459. if (!IsTruncated(&lpsRowSet->__ptr[i].__ptr[k])) {
  460. ++i;
  461. continue;
  462. }
  463. // We only want one column in this row
  464. mapColumns.clear();
  465. mapColumns.insert(std::make_pair(lpsPropTagArray->__ptr[k], k));
  466. // Un-truncate this value
  467. er = QueryRowDataByRow(lpThis, soap, lpSession, row, i, mapColumns, false, lpsRowSet);
  468. if (er != erSuccess)
  469. goto exit;
  470. ++i;
  471. }
  472. }
  473. }
  474. for (k = 0; k < lpsPropTagArray->__size; ++k) {
  475. // Do any post-processing operations
  476. if (ECGenProps::IsPropComputed(lpsPropTagArray->__ptr[k],
  477. lpODStore->ulObjType) != erSuccess)
  478. continue;
  479. i = 0;
  480. for (const auto &row : *lpRowList) {
  481. if (row.ulObjId == 0) {
  482. ++i;
  483. continue;
  484. }
  485. // Do not change category values!
  486. ECGenProps::GetPropComputed(soap, lpODStore->ulObjType,
  487. lpsPropTagArray->__ptr[k], row.ulObjId,
  488. &lpsRowSet->__ptr[i].__ptr[k]);
  489. ++i;
  490. }
  491. }
  492. *lppRowSet = lpsRowSet;
  493. lpsRowSet = NULL;
  494. exit:
  495. if (soap == NULL && lpsRowSet)
  496. FreeRowSet(lpsRowSet, true);
  497. return er;
  498. }
  499. ECRESULT ECStoreObjectTable::QueryRowDataByRow(ECGenericObjectTable *lpThis,
  500. struct soap *soap, ECSession *lpSession, const sObjectTableKey &sKey,
  501. unsigned int ulRowNum,
  502. std::multimap<unsigned int, unsigned int> &mapColumns, bool bTableLimit,
  503. struct rowSet *lpsRowSet)
  504. {
  505. ECRESULT er = erSuccess;
  506. DB_RESULT lpDBResult;
  507. DB_ROW lpDBRow = NULL;
  508. DB_LENGTHS lpDBLen = NULL;
  509. std::string strQuery;
  510. std::string strSubquery;
  511. ECDatabase *lpDatabase = NULL;
  512. bool bNeedProps = false;
  513. bool bNeedMVProps = false;
  514. bool bNeedMVIProps = false;
  515. bool bNeedSubQueries = false;
  516. std::string strSubQuery;
  517. std::string strCol;
  518. std::set<unsigned int> setSubQueries;
  519. // Select correct property column query according to whether we want to truncate or not
  520. std::string strPropColOrder = bTableLimit ? PROPCOLORDER_TRUNCATED : PROPCOLORDER;
  521. std::string strMVIPropColOrder = bTableLimit ? MVIPROPCOLORDER_TRUNCATED : MVIPROPCOLORDER;
  522. assert(lpsRowSet != NULL);
  523. er = lpSession->GetDatabase(&lpDatabase);
  524. if (er != erSuccess)
  525. return er;
  526. g_lpStatsCollector->Increment(SCN_DATABASE_ROW_READS, 1);
  527. for (const auto &col : mapColumns) {
  528. unsigned int ulPropTag = col.first;
  529. // Remember the types of columns being requested for later use
  530. if(ECGenProps::GetPropSubquery(ulPropTag, strSubQuery) == erSuccess) {
  531. setSubQueries.insert(ulPropTag);
  532. bNeedSubQueries = true;
  533. } else if(ulPropTag & MV_FLAG) {
  534. if(ulPropTag & MV_INSTANCE)
  535. bNeedMVIProps = true;
  536. else
  537. bNeedMVProps = true;
  538. } else {
  539. bNeedProps = true;
  540. }
  541. }
  542. // Do normal properties
  543. if(bNeedProps) {
  544. // mapColumns now contains the data that we still need to request from the database for this row.
  545. strQuery = "SELECT " + strPropColOrder + " FROM properties WHERE hierarchyid="+stringify(sKey.ulObjId) + " AND properties.tag IN (";
  546. for (const auto &col : mapColumns) {
  547. // A subquery is defined for this type, we don't have to get the data in the normal way.
  548. if (setSubQueries.find(col.first) != setSubQueries.cend())
  549. continue;
  550. if ((col.first & MV_FLAG) == 0) {
  551. strQuery += stringify(PROP_ID(col.first));
  552. strQuery += ",";
  553. }
  554. }
  555. // Remove trailing ,
  556. strQuery.resize(strQuery.size()-1);
  557. strQuery += ")";
  558. }
  559. // Do MV properties
  560. if(bNeedMVProps) {
  561. if(!strQuery.empty())
  562. strQuery += " UNION ";
  563. strQuery += "SELECT " + (std::string)MVPROPCOLORDER + " FROM mvproperties WHERE hierarchyid="+stringify(sKey.ulObjId) + " AND mvproperties.tag IN (";
  564. for (const auto &col : mapColumns) {
  565. if (setSubQueries.find(col.first) != setSubQueries.cend())
  566. continue;
  567. if ((col.first & MV_FLAG) && !(col.first & MV_INSTANCE)) {
  568. strQuery += stringify(PROP_ID(col.first));
  569. strQuery += ",";
  570. }
  571. }
  572. strQuery.resize(strQuery.size()-1);
  573. strQuery += ")";
  574. strQuery += " GROUP BY hierarchyid, tag";
  575. }
  576. // Do MVI properties. Output from this part is handled exactly the same as normal properties
  577. if(bNeedMVIProps) {
  578. if(!strQuery.empty())
  579. strQuery += " UNION ";
  580. strQuery += "SELECT " + strMVIPropColOrder + " FROM mvproperties WHERE hierarchyid="+stringify(sKey.ulObjId);
  581. for (const auto &col : mapColumns) {
  582. if (setSubQueries.find(col.first) != setSubQueries.cend())
  583. continue;
  584. if ((col.first & MV_FLAG) && (col.first & MV_INSTANCE)) {
  585. strQuery += " AND (";
  586. strQuery += "orderid = " + stringify(sKey.ulOrderId) + " AND ";
  587. strQuery += "tag = " + stringify(PROP_ID(col.first));
  588. strQuery += ")";
  589. }
  590. }
  591. }
  592. // Do generated properties
  593. if(bNeedSubQueries) {
  594. std::string strPropColOrder;
  595. for (const auto &col : mapColumns) {
  596. if (ECGenProps::GetPropSubquery(col.first, strSubQuery) != erSuccess)
  597. continue;
  598. strPropColOrder = GetPropColOrder(col.first, strSubQuery);
  599. if (!strQuery.empty())
  600. strQuery += " UNION ";
  601. strQuery += " SELECT " + strPropColOrder +
  602. " FROM hierarchy WHERE hierarchy.id = " +
  603. stringify(sKey.ulObjId);
  604. }
  605. }
  606. if(!strQuery.empty()) {
  607. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  608. if(er != erSuccess)
  609. return er;
  610. while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != 0) {
  611. if(lpDBRow[1] == NULL || lpDBRow[2] == NULL) {
  612. assert(false);
  613. continue;
  614. }
  615. lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
  616. unsigned int ulPropTag = PROP_TAG(atoui(lpDBRow[2]), atoui(lpDBRow[1]));
  617. // The same column may have been requested multiple times. If that is the case, SQL will give us one result for all columns. This
  618. // means we have to loop through all the same-property columns and add the same data everywhere.
  619. for (auto iterColumns = mapColumns.lower_bound(NormalizeDBPropTag(ulPropTag));
  620. iterColumns != mapColumns.cend() && CompareDBPropTag(iterColumns->first, ulPropTag); ) {
  621. // free prop if we're not allocing by soap
  622. if(soap == NULL && lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag != 0) {
  623. FreePropVal(&lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second], false);
  624. memset(&lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second], 0, sizeof(lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second]));
  625. }
  626. if(CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second]) != erSuccess) {
  627. // 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)
  628. ++iterColumns;
  629. continue;
  630. }
  631. // Update property tag to requested property tag; requested type may have been PT_UNICODE while database contains PT_STRING8
  632. lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag = iterColumns->first;
  633. if ((lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag & MV_FLAG) == 0)
  634. // Cache value
  635. lpSession->GetSessionManager()->GetCacheManager()->SetCell(&sKey, iterColumns->first, &lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second]);
  636. else if ((lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag & MVI_FLAG) == MVI_FLAG)
  637. // Get rid of the MVI_FLAG
  638. lpsRowSet->__ptr[ulRowNum].__ptr[iterColumns->second].ulPropTag &= ~MVI_FLAG;
  639. // Remove from mapColumns so we know that we got a response from SQL
  640. mapColumns.erase(iterColumns++);
  641. }
  642. }
  643. }
  644. for (const auto &col : mapColumns) {
  645. assert(lpsRowSet->__ptr[ulRowNum].__ptr[col.second].ulPropTag == 0);
  646. CopyEmptyCellToSOAPPropVal(soap, col.first, &lpsRowSet->__ptr[ulRowNum].__ptr[col.second]);
  647. lpSession->GetSessionManager()->GetCacheManager()->SetCell(&sKey, col.first, &lpsRowSet->__ptr[ulRowNum].__ptr[col.second]);
  648. }
  649. return erSuccess;
  650. }
  651. /**
  652. * Read rows from tproperties table
  653. *
  654. * Since data in tproperties is truncated, this engine can only provide truncated property values.
  655. *
  656. *
  657. * @param[in] lpThis Pointer to main table object, optional (needed for categorization)
  658. * @param[in] soap Soap object to use for memory allocations, may be NULL for malloc() allocations
  659. * @param[in] lpSession Pointer to session for security context
  660. * @param[in] mapColumns Map of columns to retrieve with key = ulPropTag, value = column number
  661. * @param[in] ulFolderId Folder ID in which the rows exist
  662. * @param[in] mapObjIds Map of objects to retrieve with key = sObjectTableKey, value = row number
  663. * @param[out] lpsRowSet Row set where data will be written. Must be pre-allocated to hold all columns and rows requested
  664. */
  665. ECRESULT ECStoreObjectTable::QueryRowDataByColumn(ECGenericObjectTable *lpThis,
  666. struct soap *soap, ECSession *lpSession,
  667. const std::multimap<unsigned int, unsigned int> &mapColumns,
  668. unsigned int ulFolderId,
  669. const std::map<sObjectTableKey, unsigned int> &mapObjIds,
  670. struct rowSet *lpsRowSet)
  671. {
  672. ECRESULT er = erSuccess;
  673. std::string strQuery;
  674. std::string strHierarchyIds;
  675. std::string strTags, strMVTags, strMVITags;
  676. std::set<std::pair<unsigned int, unsigned int> > setDone;
  677. sObjectTableKey key;
  678. DB_RESULT lpDBResult;
  679. DB_ROW lpDBRow = NULL;
  680. DB_LENGTHS lpDBLen = NULL;
  681. unsigned int ulTag = 0;
  682. unsigned int ulType = 0;
  683. ULONG ulMin, ulMax;
  684. ECDatabase *lpDatabase = NULL;
  685. std::set<unsigned int> setSubQueries;
  686. std::string strSubquery;
  687. std::string strPropColOrder;
  688. if (mapColumns.empty() || mapObjIds.empty())
  689. return erSuccess;
  690. er = lpSession->GetDatabase(&lpDatabase);
  691. if (er != erSuccess)
  692. return er;
  693. ulMin = ulMax = PROP_ID(mapColumns.begin()->first);
  694. // Split columns into MV columns and non-MV columns
  695. for (const auto &col : mapColumns) {
  696. if ((col.first & MVI_FLAG) == MVI_FLAG) {
  697. if (!strMVITags.empty())
  698. strMVITags += ",";
  699. strMVITags += stringify(PROP_ID(col.first));
  700. }
  701. if ((col.first & MVI_FLAG) == MV_FLAG) {
  702. if (!strMVTags.empty())
  703. strMVTags += ",";
  704. strMVTags += stringify(PROP_ID(col.first));
  705. }
  706. if ((col.first & MVI_FLAG) == 0) {
  707. if (ECGenProps::GetPropSubquery(col.first, strSubquery) == erSuccess) {
  708. setSubQueries.insert(col.first);
  709. } else {
  710. if (!strTags.empty())
  711. strTags += ",";
  712. strTags += stringify(PROP_ID(col.first));
  713. }
  714. }
  715. ulMin = min(ulMin, PROP_ID(col.first));
  716. ulMax = max(ulMax, PROP_ID(col.first));
  717. }
  718. for (const auto &ob : mapObjIds) {
  719. if (!strHierarchyIds.empty())
  720. strHierarchyIds += ",";
  721. strHierarchyIds += stringify(ob.first.ulObjId);
  722. }
  723. // Get data
  724. if (!strTags.empty())
  725. 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);
  726. if(!strMVTags.empty()) {
  727. if(!strQuery.empty())
  728. strQuery += " UNION ";
  729. strQuery += "SELECT " MVPROPCOLORDER ", hierarchyid, 0 FROM mvproperties WHERE hierarchyid IN(" + strHierarchyIds + ") AND tag IN (" + strMVTags +") GROUP BY hierarchyid, tag";
  730. }
  731. if(!strMVITags.empty()) {
  732. if(!strQuery.empty())
  733. strQuery += " UNION ";
  734. strQuery += "SELECT " MVIPROPCOLORDER ", hierarchyid, orderid FROM mvproperties WHERE hierarchyid IN(" + strHierarchyIds + ") AND tag IN (" +strMVITags +")";
  735. }
  736. if(!setSubQueries.empty()) {
  737. for (const auto &sq : setSubQueries) {
  738. if(!strQuery.empty())
  739. strQuery += " UNION ";
  740. if (ECGenProps::GetPropSubquery(sq, strSubquery) != erSuccess)
  741. continue;
  742. strPropColOrder = GetPropColOrder(sq, strSubquery);
  743. strQuery += " SELECT " + strPropColOrder + ", hierarchy.id, 0 FROM hierarchy WHERE hierarchy.id IN (" + strHierarchyIds + ")";
  744. }
  745. }
  746. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  747. if (er != erSuccess)
  748. return er;
  749. while((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL) {
  750. lpDBLen = lpDatabase->FetchRowLengths(lpDBResult);
  751. if(lpDBRow[FIELD_NR_MAX] == NULL || lpDBRow[FIELD_NR_MAX+1] == NULL || lpDBRow[FIELD_NR_TAG] == NULL || lpDBRow[FIELD_NR_TYPE] == NULL)
  752. continue; // No hierarchyid, tag or orderid (?)
  753. key.ulObjId = atoui(lpDBRow[FIELD_NR_MAX]);
  754. key.ulOrderId = atoui(lpDBRow[FIELD_NR_MAX+1]);
  755. ulTag = atoui(lpDBRow[FIELD_NR_TAG]);
  756. ulType = atoui(lpDBRow[FIELD_NR_TYPE]);
  757. // Find the right place to put things.
  758. // 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
  759. // while it was not requested. If that happens, then we should just discard the data.
  760. // In a non-MVI column, orderID from the DB is 0, and we should write that value into all rows with this object ID.
  761. // The lower_bound makes sure that if the requested row had order ID 1, we can still find it.
  762. std::map<sObjectTableKey, unsigned int>::const_iterator iterObjIds;
  763. if(ulType & MVI_FLAG)
  764. iterObjIds = mapObjIds.find(key);
  765. else {
  766. assert(key.ulOrderId == 0);
  767. iterObjIds = mapObjIds.lower_bound(key);
  768. }
  769. if (iterObjIds == mapObjIds.cend())
  770. continue; // Got data for a row we didn't request ? (Possible for MVI queries)
  771. while (iterObjIds != mapObjIds.cend() &&
  772. iterObjIds->first.ulObjId == key.ulObjId) {
  773. // WARNING. For PT_UNICODE columns, ulTag contains PT_STRING8, since that is the tag in the database. We rely
  774. // on PT_UNICODE = PT_STRING8 + 1 here since we do a lower_bound to scan for either PT_STRING8 or PT_UNICODE
  775. // and then use CompareDBPropTag to check the actual type in the while loop later. Same goes for PT_MV_UNICODE.
  776. auto iterColumns = mapColumns.lower_bound(PROP_TAG(ulType, ulTag));
  777. while (iterColumns != mapColumns.cend() &&
  778. CompareDBPropTag(iterColumns->first, PROP_TAG(ulType, ulTag))) {
  779. // free prop if we're not allocing by soap
  780. if(soap == NULL && lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag != 0) {
  781. FreePropVal(&lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second], false);
  782. memset(&lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second], 0, sizeof(lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]));
  783. }
  784. // 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
  785. // requested. Note that requesting the same ROW more than once is not supported (it is a map, not a multimap)
  786. if (CopyDatabasePropValToSOAPPropVal(soap, lpDBRow, lpDBLen, &lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]) != erSuccess)
  787. CopyEmptyCellToSOAPPropVal(soap, iterColumns->first, &lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]);
  788. // Update propval to correct value. We have to do this because we may have requested PT_UNICODE while the database
  789. // contains PT_STRING8.
  790. if (PROP_TYPE(lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag) == PT_ERROR)
  791. lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(iterColumns->first));
  792. else
  793. lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag = iterColumns->first;
  794. if ((lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag & MV_FLAG) == 0)
  795. lpSession->GetSessionManager()->GetCacheManager()->SetCell((sObjectTableKey*)&iterObjIds->first, iterColumns->first, &lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second]);
  796. else if ((lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag & MVI_FLAG) == MVI_FLAG)
  797. lpsRowSet->__ptr[iterObjIds->second].__ptr[iterColumns->second].ulPropTag &= ~MVI_FLAG;
  798. setDone.insert(std::make_pair(iterObjIds->second, iterColumns->second));
  799. ++iterColumns;
  800. }
  801. // 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
  802. // value on each row
  803. if(ulType & MVI_FLAG)
  804. break; // For the MVI column, we should get one result for each row
  805. else
  806. ++iterObjIds; // For non-MVI columns, we get one result for all expanded rows
  807. }
  808. }
  809. for (const auto &col : mapColumns)
  810. for (const auto &ob : mapObjIds)
  811. if (setDone.count(std::make_pair(ob.second, col.second)) == 0) {
  812. // We may be overwriting a value that was retrieved from the cache before.
  813. if (soap == NULL && lpsRowSet->__ptr[ob.second].__ptr[col.second].ulPropTag != 0)
  814. FreePropVal(&lpsRowSet->__ptr[ob.second].__ptr[col.second], false);
  815. CopyEmptyCellToSOAPPropVal(soap, col.first, &lpsRowSet->__ptr[ob.second].__ptr[col.second]);
  816. lpSession->GetSessionManager()->GetCacheManager()->SetCell(const_cast<sObjectTableKey *>(&ob.first),
  817. col.first, &lpsRowSet->__ptr[ob.second].__ptr[col.second]);
  818. }
  819. return erSuccess;
  820. }
  821. ECRESULT ECStoreObjectTable::CopyEmptyCellToSOAPPropVal(struct soap *soap, unsigned int ulPropTag, struct propVal *lpPropVal)
  822. {
  823. ECRESULT er = erSuccess;
  824. lpPropVal->ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(ulPropTag));
  825. lpPropVal->__union = SOAP_UNION_propValData_ul;
  826. lpPropVal->Value.ul = KCERR_NOT_FOUND;
  827. return er;
  828. }
  829. ECRESULT ECStoreObjectTable::GetMVRowCount(unsigned int ulObjId, unsigned int *lpulCount)
  830. {
  831. ECRESULT er = erSuccess;
  832. DB_RESULT lpDBResult;
  833. DB_ROW lpRow = NULL;
  834. std::string strQuery, strColName;
  835. int j;
  836. ECObjectTableList listRows;
  837. ECDatabase *lpDatabase = NULL;
  838. ulock_rec biglock(m_hLock);
  839. er = lpSession->GetDatabase(&lpDatabase);
  840. if (er != erSuccess)
  841. goto exit;
  842. // scan for MV-props and add rows
  843. strQuery = "SELECT count(h.id) FROM hierarchy as h";
  844. j=0;
  845. for (auto tag : m_listMVSortCols) {
  846. strColName = "col" + stringify(tag);
  847. strQuery += " LEFT JOIN mvproperties as " + strColName +
  848. " ON h.id=" + strColName + ".hierarchyid AND " +
  849. strColName + ".tag=" +
  850. stringify(PROP_ID(tag)) + " AND " + strColName +
  851. ".type=" + stringify(PROP_TYPE(NormalizeDBPropTag(tag) &~MV_INSTANCE));
  852. ++j;
  853. }
  854. strQuery += " WHERE h.id="+stringify(ulObjId)+" ORDER by h.id, orderid";
  855. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  856. if(er != erSuccess)
  857. goto exit;
  858. lpRow = lpDatabase->FetchRow(lpDBResult);
  859. if(lpRow == NULL || lpRow[0] == NULL) {
  860. er = KCERR_DATABASE_ERROR;
  861. ec_log_err("ECStoreObjectTable::GetMVRowCount(): row or column null");
  862. goto exit;
  863. }
  864. *lpulCount = atoi(lpRow[0]);
  865. exit:
  866. biglock.unlock();
  867. return er;
  868. }
  869. ECRESULT ECStoreObjectTable::Load()
  870. {
  871. ECRESULT er = erSuccess;
  872. ECDatabase *lpDatabase = NULL;
  873. DB_RESULT lpDBResult;
  874. DB_ROW lpDBRow = NULL;
  875. std::string strQuery;
  876. auto lpData = static_cast<ECODStore *>(m_lpObjectData);
  877. sObjectTableKey sRowItem;
  878. unsigned int ulFlags = lpData->ulFlags;
  879. unsigned int ulFolderId = lpData->ulFolderId;
  880. unsigned int ulObjType = lpData->ulObjType;
  881. unsigned int ulMaxItems = atoui(lpSession->GetSessionManager()->GetConfig()->GetSetting("folder_max_items"));
  882. unsigned int i;
  883. std::list<unsigned int> lstObjIds;
  884. er = lpSession->GetDatabase(&lpDatabase);
  885. if (er != erSuccess)
  886. return er;
  887. if (ulFolderId == 0)
  888. return erSuccess;
  889. // Clear old entries
  890. Clear();
  891. // Load the table with all the objects of type ulObjType and flags ulFlags in container ulParent
  892. strQuery = "SELECT hierarchy.id FROM hierarchy WHERE hierarchy.parent=" + stringify(ulFolderId);
  893. if(ulObjType == MAPI_MESSAGE)
  894. {
  895. strQuery += " AND hierarchy.type = " + stringify(ulObjType);
  896. if((ulFlags&MSGFLAG_DELETED) == 0)// Normal message and associated message
  897. strQuery += " AND hierarchy.flags & "+stringify(MSGFLAG_ASSOCIATED)+" = " + stringify(ulFlags&MSGFLAG_ASSOCIATED) + " AND hierarchy.flags & "+stringify(MSGFLAG_DELETED)+" = 0";
  898. else
  899. strQuery += " AND hierarchy.flags & "+stringify(MSGFLAG_ASSOCIATED)+" = " + stringify(ulFlags&MSGFLAG_ASSOCIATED) + " AND hierarchy.flags & "+stringify(MSGFLAG_DELETED)+" = " + stringify(MSGFLAG_DELETED);
  900. }
  901. else if(ulObjType == MAPI_FOLDER) {
  902. strQuery += " AND hierarchy.type = " + stringify(ulObjType);
  903. strQuery += " AND hierarchy.flags & "+stringify(MSGFLAG_DELETED)+" = " + stringify(ulFlags&MSGFLAG_DELETED);
  904. }else if(ulObjType == MAPI_MAILUSER) { //Read MAPI_MAILUSER and MAPI_DISTLIST
  905. strQuery += " AND (hierarchy.type = " + stringify(ulObjType) + " OR hierarchy.type = " + stringify(MAPI_DISTLIST) + ")";
  906. }else {
  907. strQuery += " AND hierarchy.type = " + stringify(ulObjType);
  908. }
  909. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  910. if(er != erSuccess)
  911. return er;
  912. i = 0;
  913. while(1) {
  914. lpDBRow = lpDatabase->FetchRow(lpDBResult);
  915. if(lpDBRow == NULL)
  916. break;
  917. if(lpDBRow[0] == NULL)
  918. continue;
  919. // Altough we don't want more than ulMaxItems entries, keep looping to get all the results from MySQL. We need to do this
  920. // because otherwise we can get out of sync with mysql which is still sending us results while we have already stopped
  921. // reading data.
  922. if(i > ulMaxItems)
  923. continue;
  924. lstObjIds.push_back(atoi(lpDBRow[0]));
  925. ++i;
  926. }
  927. LoadRows(&lstObjIds, 0);
  928. return erSuccess;
  929. }
  930. ECRESULT ECStoreObjectTable::CheckPermissions(unsigned int ulObjId)
  931. {
  932. ECRESULT er = erSuccess;
  933. unsigned int ulParent = 0;
  934. auto lpData = static_cast<ECODStore *>(m_lpObjectData);
  935. if (m_ulObjType == MAPI_FOLDER)
  936. return lpSession->GetSecurity()->CheckPermission(ulObjId, ecSecurityFolderVisible);
  937. if (m_ulObjType != MAPI_MESSAGE)
  938. return erSuccess;
  939. if (lpData->ulFolderId) {
  940. // We can either see all messages or none at all. Do this check once only as an optimisation.
  941. if (!this->fPermissionRead) {
  942. this->ulPermission = lpSession->GetSecurity()->CheckPermission(lpData->ulFolderId, ecSecurityRead);
  943. this->fPermissionRead = true;
  944. }
  945. return this->ulPermission;
  946. }
  947. // Get the parent id of the row we're inserting (this is very probably cached from Load())
  948. er = lpSession->GetSessionManager()->GetCacheManager()->GetParent(ulObjId, &ulParent);
  949. if (er != erSuccess)
  950. return er;
  951. // This will be cached after the first row in the table is added
  952. return lpSession->GetSecurity()->CheckPermission(ulParent, ecSecurityRead);
  953. }
  954. ECRESULT ECStoreObjectTable::AddRowKey(ECObjectTableList* lpRows, unsigned int *lpulLoaded, unsigned int ulFlags, bool bLoad, bool bOverride, struct restrictTable *lpOverride)
  955. {
  956. ECRESULT er = erSuccess;
  957. GUID guidServer;
  958. auto lpODStore = static_cast<ECODStore *>(m_lpObjectData);
  959. std::list<unsigned int> lstIndexerResults;
  960. std::list<unsigned int> lstFolders;
  961. std::set<unsigned int> setMatches;
  962. ECObjectTableList sMatchedRows;
  963. ECDatabase *lpDatabase = NULL;
  964. struct restrictTable *lpNewRestrict = NULL;
  965. std::string suggestion;
  966. assert(!bOverride); // Default implementation never has override enabled, so we should never see this
  967. assert(lpOverride == NULL);
  968. ulock_rec biglock(m_hLock);
  969. // Use normal table update code if:
  970. // - not an initial load (but a table update)
  971. // - no restriction
  972. // - not a restriction on a folder (eg searchfolder)
  973. if(!bLoad || !lpsRestrict || !lpODStore->ulFolderId || !lpODStore->ulStoreId || (lpODStore->ulFlags & MAPI_ASSOCIATED)) {
  974. er = ECGenericObjectTable::AddRowKey(lpRows, lpulLoaded, ulFlags, bLoad, false, NULL);
  975. } else {
  976. // Attempt to use the indexer
  977. er = lpSession->GetSessionManager()->GetServerGUID(&guidServer);
  978. if(er != erSuccess)
  979. goto exit;
  980. lstFolders.push_back(lpODStore->ulFolderId);
  981. er = lpSession->GetDatabase(&lpDatabase);
  982. if(er != erSuccess)
  983. goto exit;
  984. if (GetIndexerResults(lpDatabase, lpSession->GetSessionManager()->GetConfig(), lpSession->GetSessionManager()->GetCacheManager(), &guidServer, lpODStore->lpGuid, lstFolders, lpsRestrict, &lpNewRestrict, lstIndexerResults, suggestion) != erSuccess) {
  985. // Cannot handle this restriction with the indexer, use 'normal' restriction code
  986. // Reasons can be:
  987. // - restriction too complex
  988. // - folder not indexed
  989. // - indexer not running / not configured
  990. er = ECGenericObjectTable::AddRowKey(lpRows, lpulLoaded, ulFlags, bLoad, false, NULL);
  991. goto exit;
  992. }
  993. // Put the results in setMatches
  994. std::copy(lstIndexerResults.begin(), lstIndexerResults.end(), std::inserter(setMatches, setMatches.begin()));
  995. /* Filter the incoming row set with the matches.
  996. *
  997. * Actually, we should not have to do this. We could just take the result set from the indexer and
  998. * feed that directly to AddRowKey. However, the indexer may be slightly 'behind' in time, and the
  999. * list of objects in lpRows is more up-to-date. To make sure that we will not generate 'phantom' rows
  1000. * of deleted items we take the cross section of lpRows and setMatches. In most cases the result
  1001. * will be equal to setMatches though.
  1002. */
  1003. for (const auto &row : *lpRows)
  1004. if (setMatches.find(row.ulObjId) != setMatches.end())
  1005. sMatchedRows.push_back(row);
  1006. // Pass filtered results to AddRowKey, which will perform any further filtering required
  1007. er = ECGenericObjectTable::AddRowKey(&sMatchedRows, lpulLoaded, ulFlags, bLoad, true, lpNewRestrict);
  1008. if(er != erSuccess)
  1009. goto exit;
  1010. }
  1011. exit:
  1012. biglock.unlock();
  1013. if(lpNewRestrict)
  1014. FreeRestrictTable(lpNewRestrict);
  1015. return er;
  1016. }
  1017. /**
  1018. * Get all deferred changes for a specific folder
  1019. */
  1020. ECRESULT GetDeferredTableUpdates(ECDatabase *lpDatabase, unsigned int ulFolderId, std::list<unsigned int> *lpDeferred)
  1021. {
  1022. ECRESULT er = erSuccess;
  1023. DB_RESULT lpDBResult;
  1024. DB_ROW lpDBRow = NULL;
  1025. lpDeferred->clear();
  1026. std::string strQuery = "SELECT hierarchyid FROM deferredupdate WHERE folderid = " + stringify(ulFolderId);
  1027. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1028. if(er != erSuccess)
  1029. return er;
  1030. while ((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL)
  1031. lpDeferred->push_back(atoui(lpDBRow[0]));
  1032. return erSuccess;
  1033. }
  1034. /**
  1035. * Get all deferred changes for a specific folder
  1036. */
  1037. ECRESULT GetDeferredTableUpdates(ECDatabase *lpDatabase, ECObjectTableList* lpRowList, std::list<unsigned int> *lpDeferred)
  1038. {
  1039. ECRESULT er = erSuccess;
  1040. DB_RESULT lpDBResult;
  1041. DB_ROW lpDBRow = NULL;
  1042. lpDeferred->clear();
  1043. std::string strQuery = "SELECT hierarchyid FROM deferredupdate WHERE hierarchyid IN( ";
  1044. for (const auto &row : *lpRowList) {
  1045. strQuery += stringify(row.ulObjId);
  1046. strQuery += ",";
  1047. }
  1048. // Remove trailing comma
  1049. strQuery.resize(strQuery.size()-1);
  1050. strQuery += ")";
  1051. er = lpDatabase->DoSelect(strQuery, &lpDBResult);
  1052. if(er != erSuccess)
  1053. return er;
  1054. while ((lpDBRow = lpDatabase->FetchRow(lpDBResult)) != NULL)
  1055. lpDeferred->push_back(atoui(lpDBRow[0]));
  1056. return erSuccess;
  1057. }
  1058. } /* namespace */