ECDatabaseMySQL.cpp 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  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 <kopano/zcdefs.h>
  18. #include <kopano/platform.h>
  19. #include <iostream>
  20. #include <errmsg.h>
  21. #include "mysqld_error.h"
  22. #include <kopano/stringutil.h>
  23. #include <kopano/ECDefs.h>
  24. #include "ECDBDef.h"
  25. #include "ECUserManagement.h"
  26. #include <kopano/ECConfig.h>
  27. #include <kopano/ECLogger.h>
  28. #include <kopano/MAPIErrors.h>
  29. #include <kopano/kcodes.h>
  30. #include <kopano/ecversion.h>
  31. #include <mapidefs.h>
  32. #include "ECConversion.h"
  33. #include "ECDatabase.h"
  34. #include "SOAPUtils.h"
  35. #include "ECSearchFolders.h"
  36. #include "ECDatabaseUpdate.h"
  37. #include "ECStatsCollector.h"
  38. using namespace std;
  39. namespace KC {
  40. #ifdef DEBUG
  41. #define DEBUG_SQL 0
  42. #define DEBUG_TRANSACTION 0
  43. #endif
  44. struct sUpdateList_t {
  45. unsigned int ulVersion;
  46. unsigned int ulVersionMin; // Version to start the update
  47. const char *lpszLogComment;
  48. ECRESULT (*lpFunction)(ECDatabase* lpDatabase);
  49. };
  50. class zcp_versiontuple _kc_final {
  51. public:
  52. zcp_versiontuple(unsigned int maj = 0, unsigned int min = 0,
  53. unsigned int mic = 0, unsigned int rev = 0, unsigned int dbs = 0) :
  54. v_major(maj), v_minor(min), v_micro(mic),
  55. v_rev(rev), v_schema(dbs)
  56. {
  57. }
  58. std::string stringify(char sep = '.') const;
  59. int compare(const zcp_versiontuple &) const;
  60. /* stupid major(3) function uses a #define in glibc */
  61. unsigned int v_major, v_minor, v_micro, v_rev, v_schema;
  62. };
  63. static const sUpdateList_t sUpdateList[] = {
  64. // Updates from version 5.02 to 5.10
  65. { Z_UPDATE_CREATE_VERSIONS_TABLE, 0, "Create table: versions", UpdateDatabaseCreateVersionsTable },
  66. { Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE, 0, "Create table: searchresults", UpdateDatabaseCreateSearchFolders },
  67. { Z_UPDATE_FIX_USERTABLE_NONACTIVE, 0, "Update table: users, field: nonactive", UpdateDatabaseFixUserNonActive },
  68. // Only update if previous revision was after Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE, because the definition has changed
  69. { Z_UPDATE_ADD_FLAGS_TO_SEARCHRESULTS, Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE, "Update table: searchresults", UpdateDatabaseCreateSearchFoldersFlags },
  70. { Z_UPDATE_POPULATE_SEARCHFOLDERS, 0, "Populate search folders", UpdateDatabasePopulateSearchFolders },
  71. // Updates from version 5.10 to 5.20
  72. { Z_UPDATE_CREATE_CHANGES_TABLE, 0, "Create table: changes", UpdateDatabaseCreateChangesTable },
  73. { Z_UPDATE_CREATE_SYNCS_TABLE, 0, "Create table: syncs", UpdateDatabaseCreateSyncsTable },
  74. { Z_UPDATE_CREATE_INDEXEDPROPS_TABLE, 0, "Create table: indexedproperties", UpdateDatabaseCreateIndexedPropertiesTable },
  75. { Z_UPDATE_CREATE_SETTINGS_TABLE, 0, "Create table: settings", UpdateDatabaseCreateSettingsTable},
  76. { Z_UPDATE_CREATE_SERVER_GUID, 0, "Insert server GUID into settings", UpdateDatabaseCreateServerGUID },
  77. { Z_UPDATE_CREATE_SOURCE_KEYS, 0, "Insert source keys into indexedproperties", UpdateDatabaseCreateSourceKeys },
  78. // Updates from version 5.20 to 6.00
  79. { Z_UPDATE_CONVERT_ENTRYIDS, 0, "Convert entryids: indexedproperties", UpdateDatabaseConvertEntryIDs },
  80. { Z_UPDATE_CONVERT_SC_ENTRYIDLIST, 0, "Update entrylist searchcriteria", UpdateDatabaseSearchCriteria },
  81. { Z_UPDATE_CONVERT_USER_OBJECT_TYPE, 0, "Add Object type to 'users' table", UpdateDatabaseAddUserObjectType },
  82. { Z_UPDATE_ADD_USER_SIGNATURE, 0, "Add signature to 'users' table", UpdateDatabaseAddUserSignature },
  83. { Z_UPDATE_ADD_SOURCE_KEY_SETTING, 0, "Add setting 'source_key_auto_increment'", UpdateDatabaseAddSourceKeySetting },
  84. { Z_UPDATE_FIX_USERS_RESTRICTIONS, 0, "Add restriction to 'users' table", UpdateDatabaseRestrictExternId },
  85. // Update from version 6.00 to 6.10
  86. { Z_UPDATE_ADD_USER_COMPANY, 0, "Add company column to 'users' table", UpdateDatabaseAddUserCompany },
  87. { Z_UPDATE_ADD_OBJECT_RELATION_TYPE, 0, "Add Object relation type to 'objectrelation' table", UpdateDatabaseAddObjectRelationType },
  88. { Z_UPDATE_DEL_DEFAULT_COMPANY, 0, "Delete default company from 'users' table", UpdateDatabaseDelUserCompany},
  89. { Z_UPDATE_ADD_COMPANY_TO_STORES, 0, "Adding company to 'stores' table", UpdateDatabaseAddCompanyToStore},
  90. // Update from version x to x
  91. { Z_UPDATE_ADD_IMAP_SEQ, 0, "Add IMAP sequence number in 'settings' table", UpdateDatabaseAddIMAPSequenceNumber},
  92. { Z_UPDATE_KEYS_CHANGES, Z_UPDATE_CREATE_CHANGES_TABLE, "Update keys in 'changes' table", UpdateDatabaseKeysChanges},
  93. // Update from version 6.1x to 6.20
  94. { Z_UPDATE_MOVE_PUBLICFOLDERS, 0, "Moving publicfolders and favorites", UpdateDatabaseMoveFoldersInPublicFolder},
  95. // Update from version 6.2x to 6.30
  96. { Z_UPDATE_ADD_EXTERNID_TO_OBJECT, 0, "Adding externid to 'object' table", UpdateDatabaseAddExternIdToObject},
  97. { Z_UPDATE_CREATE_REFERENCES, 0, "Creating Single Instance Attachment table", UpdateDatabaseCreateReferences},
  98. { Z_UPDATE_LOCK_DISTRIBUTED, 0, "Locking multiserver capability", UpdateDatabaseLockDistributed},
  99. { Z_UPDATE_CREATE_ABCHANGES_TABLE, 0, "Creating Addressbook Changes table", UpdateDatabaseCreateABChangesTable},
  100. { Z_UPDATE_SINGLEINSTANCE_TAG, 0, "Updating 'singleinstances' table to correct tag value", UpdateDatabaseSetSingleinstanceTag},
  101. // Update from version 6.3x to 6.40
  102. { Z_UPDATE_CREATE_SYNCEDMESSAGES_TABLE, 0, "Create table: synced messages", UpdateDatabaseCreateSyncedMessagesTable},
  103. // Update from < 6.30 to >= 6.30
  104. { Z_UPDATE_FORCE_AB_RESYNC, 0, "Force Addressbook Resync", UpdateDatabaseForceAbResync},
  105. // Update from version 6.3x to 6.40
  106. { Z_UPDATE_RENAME_OBJECT_TYPE_TO_CLASS, 0, "Rename objecttype columns to objectclass", UpdateDatabaseRenameObjectTypeToObjectClass},
  107. { Z_UPDATE_CONVERT_OBJECT_TYPE_TO_CLASS, 0, "Convert objecttype columns to objectclass values", UpdateDatabaseConvertObjectTypeToObjectClass},
  108. { Z_UPDATE_ADD_OBJECT_MVPROPERTY_TABLE, 0, "Add object MV property table", UpdateDatabaseAddMVPropertyTable},
  109. { Z_UPDATE_COMPANYNAME_TO_COMPANYID, 0, "Link objects in DB plugin through companyid", UpdateDatabaseCompanyNameToCompanyId},
  110. { Z_UPDATE_OUTGOINGQUEUE_PRIMARY_KEY, 0, "Update outgoingqueue key", UpdateDatabaseOutgoingQueuePrimarykey},
  111. { Z_UPDATE_ACL_PRIMARY_KEY, 0, "Update acl key", UpdateDatabaseACLPrimarykey},
  112. { Z_UPDATE_BLOB_EXTERNID, 0, "Update externid in object table", UpdateDatabaseBlobExternId}, // Avoid MySQL 4.x traling spaces quirk
  113. { Z_UPDATE_KEYS_CHANGES_2, 0, "Update keys in 'changes' table", UpdateDatabaseKeysChanges2},
  114. { Z_UPDATE_MVPROPERTIES_PRIMARY_KEY, 0, "Update mvproperties key", UpdateDatabaseMVPropertiesPrimarykey},
  115. { Z_UPDATE_FIX_SECURITYGROUP_DBPLUGIN, 0, "Update DB plugin group to security groups", UpdateDatabaseFixDBPluginGroups},
  116. { Z_UPDATE_CONVERT_SENDAS_DBPLUGIN, 0, "Update DB/Unix plugin sendas settings", UpdateDatabaseFixDBPluginSendAs},
  117. { Z_UPDATE_MOVE_IMAP_SUBSCRIBES, 0, "Move IMAP subscribed list from store to inbox", UpdateDatabaseMoveSubscribedList},
  118. { Z_UPDATE_SYNC_TIME_KEY, 0, "Update sync table time index", UpdateDatabaseSyncTimeIndex },
  119. // Update within the 6.40
  120. { Z_UPDATE_ADD_STATE_KEY, 0, "Update changes table state key", UpdateDatabaseAddStateKey },
  121. // Blocking upgrade from 6.40 to 7.00, tables are not unicode compatible.
  122. { Z_UPDATE_CONVERT_TO_UNICODE, 0, "Converting database to Unicode", UpdateDatabaseConvertToUnicode },
  123. // Update from version 6.4x to 7.00
  124. { Z_UPDATE_CONVERT_STORE_USERNAME, 0, "Update stores table usernames", UpdateDatabaseConvertStoreUsername },
  125. { Z_UPDATE_CONVERT_RULES, 0, "Converting rules to Unicode", UpdateDatabaseConvertRules },
  126. { Z_UPDATE_CONVERT_SEARCH_FOLDERS, 0, "Converting search folders to Unicode", UpdateDatabaseConvertSearchFolders },
  127. { Z_UPDATE_CONVERT_PROPERTIES, 0, "Converting properties for IO performance", UpdateDatabaseConvertProperties },
  128. { Z_UPDATE_CREATE_COUNTERS, 0, "Creating counters for IO performance", UpdateDatabaseCreateCounters },
  129. { Z_UPDATE_CREATE_COMMON_PROPS, 0, "Creating common properties for IO performance", UpdateDatabaseCreateCommonProps },
  130. { Z_UPDATE_CHECK_ATTACHMENTS, 0, "Checking message attachment properties for IO performance", UpdateDatabaseCheckAttachments },
  131. { Z_UPDATE_CREATE_TPROPERTIES, 0, "Creating tproperties for IO performance", UpdateDatabaseCreateTProperties },
  132. { Z_UPDATE_CONVERT_HIERARCHY, 0, "Converting hierarchy for IO performance", UpdateDatabaseConvertHierarchy },
  133. { Z_UPDATE_CREATE_DEFERRED, 0, "Creating deferred table for IO performance", UpdateDatabaseCreateDeferred },
  134. { Z_UPDATE_CONVERT_CHANGES, 0, "Converting changes for IO performance", UpdateDatabaseConvertChanges },
  135. { Z_UPDATE_CONVERT_NAMES, 0, "Converting names table to Unicode", UpdateDatabaseConvertNames },
  136. // Update from version 7.00 to 7.0.1
  137. { Z_UPDATE_CONVERT_RF_TOUNICODE, 0, "Converting receivefolder table to Unicode", UpdateDatabaseReceiveFolderToUnicode },
  138. // Update from 6.40.13 / 7.0.3
  139. { Z_UPDATE_CREATE_CLIENTUPDATE_TABLE, 0, "Creating client update status table", UpdateDatabaseClientUpdateStatus },
  140. { Z_UPDATE_CONVERT_STORES, 0, "Converting stores table", UpdateDatabaseConvertStores },
  141. { Z_UPDATE_UPDATE_STORES, 0, "Updating stores table", UpdateDatabaseUpdateStores },
  142. // Update from 7.0 to 7.1
  143. { Z_UPDATE_UPDATE_WLINK_RECKEY, 0, "Updating wunderbar record keys", UpdateWLinkRecordKeys },
  144. // New in 7.2.2
  145. { Z_UPDATE_VERSIONTBL_MICRO, 0, "Add \"micro\" column to \"versions\" table", UpdateVersionsTbl },
  146. // New in 8.1.0 / 7.2.4, MySQL 5.7 compatibility
  147. { Z_UPDATE_ABCHANGES_PKEY, 0, "Updating abchanges table", UpdateABChangesTbl },
  148. { Z_UPDATE_CHANGES_PKEY, 0, "Updating changes table", UpdateChangesTbl },
  149. };
  150. static const char *const server_groups[] = {
  151. "kopano",
  152. NULL,
  153. };
  154. struct STOREDPROCS {
  155. const char *szName;
  156. const char *szSQL;
  157. };
  158. /**
  159. * Mode 0 = All bodies
  160. * Mode 1 = Best body only (RTF better than HTML) + plaintext
  161. * Mode 2 = Plaintext only
  162. */
  163. static const char szGetProps[] =
  164. "CREATE PROCEDURE GetProps(IN hid integer, IN mode integer)\n"
  165. "BEGIN\n"
  166. " DECLARE bestbody INT;\n"
  167. " IF mode = 1 THEN\n"
  168. " call GetBestBody(hid, bestbody);\n"
  169. " END IF;\n"
  170. " SELECT 0, tag, properties.type, val_ulong, val_string, val_binary, val_double, val_longint, val_hi, val_lo, 0, names.nameid, names.namestring, names.guid\n"
  171. " FROM properties LEFT JOIN names ON (properties.tag-0x8501)=names.id WHERE hierarchyid=hid AND (tag <= 0x8500 OR names.id IS NOT NULL) AND (tag NOT IN (0x1009, 0x1013) OR mode = 0 OR (mode = 1 AND tag = bestbody) )\n"
  172. " UNION\n"
  173. " SELECT count(*), tag, mvproperties.type, \n"
  174. " group_concat(length(mvproperties.val_ulong),':', mvproperties.val_ulong ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  175. " group_concat(length(mvproperties.val_string),':', mvproperties.val_string ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  176. " group_concat(length(mvproperties.val_binary),':', mvproperties.val_binary ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  177. " group_concat(length(mvproperties.val_double),':', mvproperties.val_double ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  178. " group_concat(length(mvproperties.val_longint),':', mvproperties.val_longint ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  179. " group_concat(length(mvproperties.val_hi),':', mvproperties.val_hi ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  180. " group_concat(length(mvproperties.val_lo),':', mvproperties.val_lo ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  181. " 0, names.nameid, names.namestring, names.guid \n"
  182. " FROM mvproperties LEFT JOIN names ON (mvproperties.tag-0x8501)=names.id WHERE hierarchyid=hid AND (tag <= 0x8500 OR names.id IS NOT NULL) GROUP BY tag, mvproperties.type; \n"
  183. "END;\n";
  184. static const char szPrepareGetProps[] =
  185. "CREATE PROCEDURE PrepareGetProps(IN hid integer)\n"
  186. "BEGIN\n"
  187. " SELECT 0, tag, properties.type, val_ulong, val_string, val_binary, val_double, val_longint, val_hi, val_lo, hierarchy.id, names.nameid, names.namestring, names.guid\n"
  188. " FROM properties JOIN hierarchy ON properties.hierarchyid=hierarchy.id LEFT JOIN names ON (properties.tag-0x8501)=names.id WHERE hierarchy.parent=hid AND (tag <= 0x8500 OR names.id IS NOT NULL);\n"
  189. " SELECT count(*), tag, mvproperties.type, \n"
  190. " group_concat(length(mvproperties.val_ulong),':', mvproperties.val_ulong ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  191. " group_concat(length(mvproperties.val_string),':', mvproperties.val_string ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  192. " group_concat(length(mvproperties.val_binary),':', mvproperties.val_binary ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  193. " group_concat(length(mvproperties.val_double),':', mvproperties.val_double ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  194. " group_concat(length(mvproperties.val_longint),':', mvproperties.val_longint ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  195. " group_concat(length(mvproperties.val_hi),':', mvproperties.val_hi ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  196. " group_concat(length(mvproperties.val_lo),':', mvproperties.val_lo ORDER BY mvproperties.orderid SEPARATOR ''), \n"
  197. " hierarchy.id, names.nameid, names.namestring, names.guid \n"
  198. " FROM mvproperties JOIN hierarchy ON mvproperties.hierarchyid=hierarchy.id LEFT JOIN names ON (mvproperties.tag-0x8501)=names.id WHERE hierarchy.parent=hid AND (tag <= 0x8500 OR names.id IS NOT NULL) GROUP BY tag, mvproperties.type; \n"
  199. "END;\n";
  200. static const char szGetBestBody[] =
  201. "CREATE PROCEDURE GetBestBody(hid integer, OUT bestbody integer)\n"
  202. "DETERMINISTIC\n"
  203. "BEGIN\n"
  204. " DECLARE best INT;\n"
  205. " DECLARE CONTINUE HANDLER FOR NOT FOUND\n"
  206. " SET bestbody = 0 ;\n"
  207. " \n"
  208. " # Get body with lowest id (RTF before HTML)\n"
  209. " SELECT tag INTO bestbody FROM properties WHERE hierarchyid=hid AND tag IN (0x1009, 0x1013) ORDER BY tag LIMIT 1;\n"
  210. "END;\n";
  211. static const char szStreamObj[] =
  212. "# Read a type-5 (Message) item from the database, output properties and subobjects\n"
  213. "CREATE PROCEDURE StreamObj(IN rootid integer, IN maxdepth integer, IN mode integer)\n"
  214. "BEGIN\n"
  215. "DECLARE no_more_rows BOOLEAN;\n"
  216. "DECLARE subid INT;\n"
  217. "DECLARE subsubid INT;\n"
  218. "DECLARE subtype INT;\n"
  219. "DECLARE cur_hierarchy CURSOR FOR\n"
  220. " SELECT id,hierarchy.type FROM hierarchy WHERE parent=rootid AND type=7; \n"
  221. "DECLARE CONTINUE HANDLER FOR NOT FOUND\n"
  222. " SET no_more_rows = TRUE;\n"
  223. " call GetProps(rootid, mode);\n"
  224. " call PrepareGetProps(rootid);\n"
  225. " SELECT id,hierarchy.type FROM hierarchy WHERE parent=rootid;\n"
  226. " OPEN cur_hierarchy;\n"
  227. " the_loop: LOOP\n"
  228. " FETCH cur_hierarchy INTO subid, subtype;\n"
  229. " IF no_more_rows THEN\n"
  230. " CLOSE cur_hierarchy;\n"
  231. " LEAVE the_loop;\n"
  232. " END IF;\n"
  233. " IF subtype = 7 THEN\n"
  234. " BEGIN\n"
  235. " DECLARE CONTINUE HANDLER FOR NOT FOUND set subsubid = 0;\n"
  236. " IF maxdepth > 0 THEN\n"
  237. " SELECT id INTO subsubid FROM hierarchy WHERE parent=subid LIMIT 1;\n"
  238. " SELECT id, hierarchy.type FROM hierarchy WHERE parent = subid LIMIT 1;\n"
  239. " IF subsubid != 0 THEN\n"
  240. " # Recurse into submessage (must be type 5 since attachments can only contain nested messages)\n"
  241. " call StreamObj(subsubid, maxdepth-1, mode);\n"
  242. " END IF;\n"
  243. " ELSE\n"
  244. " # Maximum depth reached. Output a zero-length subset to indicate that we're\n"
  245. " # not recursing further.\n"
  246. " SELECT id, hierarchy.type FROM hierarchy WHERE parent=subid LIMIT 0;\n"
  247. " END IF;\n"
  248. " END;\n"
  249. " END IF;\n"
  250. " END LOOP the_loop;\n"
  251. "END\n";
  252. static const STOREDPROCS stored_procedures[] = {
  253. { "GetProps", szGetProps },
  254. { "PrepareGetProps", szPrepareGetProps },
  255. { "GetBestBody", szGetBestBody },
  256. { "StreamObj", szStreamObj }
  257. };
  258. std::string zcp_versiontuple::stringify(char sep) const
  259. {
  260. return ::stringify(v_major) + sep + ::stringify(v_minor) + sep +
  261. ::stringify(v_micro) + sep + ::stringify(v_rev) + sep +
  262. ::stringify(v_schema);
  263. }
  264. int zcp_versiontuple::compare(const zcp_versiontuple &rhs) const
  265. {
  266. if (v_major < rhs.v_major)
  267. return -1;
  268. if (v_major > rhs.v_major)
  269. return 1;
  270. if (v_minor < rhs.v_minor)
  271. return -1;
  272. if (v_minor > rhs.v_minor)
  273. return 1;
  274. if (v_micro < rhs.v_micro)
  275. return -1;
  276. if (v_micro > rhs.v_micro)
  277. return 1;
  278. if (v_rev < rhs.v_rev)
  279. return -1;
  280. if (v_rev > rhs.v_rev)
  281. return 1;
  282. return 0;
  283. }
  284. ECDatabase::ECDatabase(ECConfig *cfg) :
  285. m_lpConfig(cfg)
  286. {
  287. }
  288. ECDatabase::~ECDatabase(void)
  289. {
  290. Close();
  291. }
  292. ECRESULT ECDatabase::InitLibrary(const char *lpDatabaseDir,
  293. const char *lpConfigFile)
  294. {
  295. string strDatabaseDir;
  296. string strConfigFile;
  297. int ret = 0;
  298. if(lpDatabaseDir) {
  299. strDatabaseDir = "--datadir=";
  300. strDatabaseDir+= lpDatabaseDir;
  301. }
  302. if(lpConfigFile) {
  303. strConfigFile = "--defaults-file=";
  304. strConfigFile+= lpConfigFile;
  305. }
  306. const char *server_args[] = {
  307. "", /* this string is not used */
  308. strConfigFile.c_str(),
  309. strDatabaseDir.c_str(),
  310. };
  311. /*
  312. * mysql's function signature stinks, and even their samples
  313. * do the cast :(
  314. */
  315. if ((ret = mysql_library_init(ARRAY_SIZE(server_args),
  316. const_cast<char **>(server_args),
  317. const_cast<char **>(server_groups))) != 0) {
  318. ec_log_crit("Unable to initialize mysql: error 0x%08X", ret);
  319. return KCERR_DATABASE_ERROR;
  320. }
  321. return erSuccess;
  322. }
  323. /**
  324. * Initialize anything that has to be initialized at server startup.
  325. *
  326. * Currently this means we're updating all the stored procedure definitions
  327. */
  328. ECRESULT ECDatabase::InitializeDBState(void)
  329. {
  330. return InitializeDBStateInner();
  331. }
  332. ECRESULT ECDatabase::InitializeDBStateInner(void)
  333. {
  334. ECRESULT er;
  335. for (size_t i = 0; i < ARRAY_SIZE(stored_procedures); ++i) {
  336. er = DoUpdate(std::string("DROP PROCEDURE IF EXISTS ") + stored_procedures[i].szName);
  337. if(er != erSuccess)
  338. return er;
  339. er = DoUpdate(stored_procedures[i].szSQL);
  340. if(er != erSuccess) {
  341. int err = mysql_errno(&m_lpMySQL);
  342. if (err == ER_DBACCESS_DENIED_ERROR) {
  343. ec_log_err("The storage server is not allowed to create stored procedures");
  344. ec_log_err("Please grant CREATE ROUTINE permissions to the mysql user \"%s\" on the \"%s\" database",
  345. m_lpConfig->GetSetting("mysql_user"), m_lpConfig->GetSetting("mysql_database"));
  346. } else {
  347. ec_log_err("The storage server is unable to create stored procedures, error %d", err);
  348. }
  349. return er;
  350. }
  351. }
  352. return erSuccess;
  353. }
  354. void ECDatabase::UnloadLibrary(void)
  355. {
  356. /*
  357. * MySQL will timeout waiting for its own threads if the mysql
  358. * initialization was done in another thread than the one where
  359. * mysql_*_end() is called. [Global problem - it also affects
  360. * projects other than Kopano's.] :(
  361. */
  362. ec_log_notice("Waiting for mysql_server_end");
  363. mysql_server_end();// mysql > 4.1.10 = mysql_library_end();
  364. ec_log_notice("Waiting for mysql_library_end");
  365. mysql_library_end();
  366. }
  367. ECRESULT ECDatabase::CheckExistColumn(const std::string &strTable,
  368. const std::string &strColumn, bool *lpbExist)
  369. {
  370. ECRESULT er = erSuccess;
  371. std::string strQuery;
  372. DB_RESULT lpDBResult;
  373. strQuery = "SELECT 1 FROM information_schema.COLUMNS "
  374. "WHERE TABLE_SCHEMA = '" + string(m_lpConfig->GetSetting("mysql_database")) + "' "
  375. "AND TABLE_NAME = '" + strTable + "' "
  376. "AND COLUMN_NAME = '" + strColumn + "'";
  377. er = DoSelect(strQuery, &lpDBResult);
  378. if (er != erSuccess)
  379. return er;
  380. *lpbExist = (FetchRow(lpDBResult) != NULL);
  381. return er;
  382. }
  383. ECRESULT ECDatabase::CheckExistIndex(const std::string &strTable,
  384. const std::string &strKey, bool *lpbExist)
  385. {
  386. ECRESULT er = erSuccess;
  387. std::string strQuery;
  388. DB_RESULT lpDBResult;
  389. DB_ROW lpRow = NULL;
  390. // WHERE not supported in MySQL < 5.0.3
  391. strQuery = "SHOW INDEXES FROM " + strTable;
  392. er = DoSelect(strQuery, &lpDBResult);
  393. if (er != erSuccess)
  394. return er;
  395. *lpbExist = false;
  396. while ((lpRow = FetchRow(lpDBResult)) != NULL) {
  397. // 2 is Key_name
  398. if (lpRow[2] && strcmp(lpRow[2], strKey.c_str()) == 0) {
  399. *lpbExist = true;
  400. break;
  401. }
  402. }
  403. return er;
  404. }
  405. ECRESULT ECDatabase::Connect(void)
  406. {
  407. auto gcm = atoui(m_lpConfig->GetSetting("mysql_group_concat_max_len"));
  408. if (gcm < 1024)
  409. gcm = 1024;
  410. /*
  411. * Set auto reconnect OFF. mysql < 5.0.4 default on, mysql 5.0.4 >
  412. * reconnection default off. We always want reconnect OFF, because we
  413. * want to know when the connection is broken since this creates a new
  414. * MySQL session, and we want to set some session variables.
  415. */
  416. auto er = KDatabase::Connect(m_lpConfig, false,
  417. CLIENT_MULTI_STATEMENTS, gcm);
  418. if (er != erSuccess)
  419. return er;
  420. if (Query("set max_sp_recursion_depth = 255") != 0) {
  421. ec_log_err("Unable to set recursion depth");
  422. er = KCERR_DATABASE_ERROR;
  423. goto exit;
  424. }
  425. if (m_lpMySQL.server_version) {
  426. // m_lpMySQL.server_version is a C type string (char*) containing something like "5.5.37-0+wheezy1" (MySQL),
  427. // "5.5.37-MariaDB-1~wheezy-log" or "10.0.11-MariaDB=1~wheezy-log" (MariaDB)
  428. // The following code may look funny, but it is correct, see http://www.cplusplus.com/reference/cstdlib/strtol/
  429. long int majorversion = strtol(m_lpMySQL.server_version, NULL, 10);
  430. // Check for over/underflow and version.
  431. if (errno != ERANGE && majorversion >= 5)
  432. /*
  433. * This option was introduced in mysql 5.0, so let's
  434. * not even try on 4.1 servers. Ignore error, if any.
  435. */
  436. Query("SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_UNSIGNED_SUBTRACTION'");
  437. }
  438. exit:
  439. if (er == erSuccess)
  440. g_lpStatsCollector->Increment(SCN_DATABASE_CONNECTS);
  441. else
  442. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_CONNECTS);
  443. return er;
  444. }
  445. /**
  446. * Perform an SQL query on MySQL
  447. *
  448. * Sends a query to the MySQL server, and does a reconnect if the server connection is lost before or during
  449. * the SQL query. The reconnect is done only once. If the query fails after the reconnect, the entire call
  450. * fails.
  451. *
  452. * It is up to the caller to get any result information from the query.
  453. *
  454. * @param[in] strQuery SQL query to perform
  455. * @return result (erSuccess or KCERR_DATABASE_ERROR)
  456. */
  457. ECRESULT ECDatabase::Query(const std::string &strQuery)
  458. {
  459. ECRESULT er = erSuccess;
  460. int err = KDatabase::Query(strQuery);
  461. if(err && (mysql_errno(&m_lpMySQL) == CR_SERVER_LOST || mysql_errno(&m_lpMySQL) == CR_SERVER_GONE_ERROR)) {
  462. ec_log_warn("SQL [%08lu] info: Try to reconnect", m_lpMySQL.thread_id);
  463. er = Close();
  464. if(er != erSuccess)
  465. return er;
  466. er = Connect();
  467. if(er != erSuccess)
  468. return er;
  469. // Try again
  470. err = mysql_real_query( &m_lpMySQL, strQuery.c_str(), strQuery.length() );
  471. }
  472. if(err) {
  473. if (!m_bSuppressLockErrorLogging || GetLastError() == DB_E_UNKNOWN)
  474. ec_log_err("SQL [%08lu] Failed: %s, Query Size: %lu, Query: \"%s\"", m_lpMySQL.thread_id, mysql_error(&m_lpMySQL), static_cast<unsigned long>(strQuery.size()), strQuery.c_str());
  475. er = KCERR_DATABASE_ERROR;
  476. // Don't assert on ER_NO_SUCH_TABLE because it's an anticipated error in the db upgrade code.
  477. if (mysql_errno(&m_lpMySQL) != ER_NO_SUCH_TABLE)
  478. assert(false);
  479. }
  480. return er;
  481. }
  482. ECRESULT ECDatabase::DoSelect(const std::string &strQuery,
  483. DB_RESULT *lppResult, bool fStreamResult)
  484. {
  485. ECRESULT er = KDatabase::DoSelect(strQuery, lppResult, fStreamResult);
  486. g_lpStatsCollector->Increment(SCN_DATABASE_SELECTS);
  487. if (er != erSuccess) {
  488. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_SELECTS);
  489. g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL));
  490. }
  491. return er;
  492. }
  493. ECRESULT ECDatabase::DoSelectMulti(const std::string &strQuery)
  494. {
  495. ECRESULT er = erSuccess;
  496. assert(strQuery.length() != 0);
  497. autolock alk(*this);
  498. if( Query(strQuery) != erSuccess ) {
  499. er = KCERR_DATABASE_ERROR;
  500. ec_log_err("ECDatabase::DoSelectMulti(): select failed");
  501. goto exit;
  502. }
  503. m_bFirstResult = true;
  504. g_lpStatsCollector->Increment(SCN_DATABASE_SELECTS);
  505. exit:
  506. if (er != erSuccess) {
  507. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_SELECTS);
  508. g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL));
  509. }
  510. return er;
  511. }
  512. /**
  513. * Get next resultset from a multi-resultset query
  514. *
  515. * @param[out] lppResult Resultset
  516. * @return result
  517. */
  518. ECRESULT ECDatabase::GetNextResult(DB_RESULT *lppResult)
  519. {
  520. ECRESULT er = erSuccess;
  521. DB_RESULT lpResult;
  522. int ret = 0;
  523. autolock alk(*this);
  524. if(!m_bFirstResult)
  525. ret = mysql_next_result( &m_lpMySQL );
  526. m_bFirstResult = false;
  527. if(ret < 0) {
  528. er = KCERR_DATABASE_ERROR;
  529. ec_log_err("SQL [%08lu] next_result failed: expected more results", m_lpMySQL.thread_id);
  530. goto exit;
  531. }
  532. if(ret > 0) {
  533. er = KCERR_DATABASE_ERROR;
  534. ec_log_err("SQL [%08lu] next_result of multi-resultset failed: %s", m_lpMySQL.thread_id, mysql_error(&m_lpMySQL));
  535. goto exit;
  536. }
  537. lpResult = DB_RESULT(this, mysql_store_result(&m_lpMySQL));
  538. if (lpResult == nullptr) {
  539. // I think this can only happen on the first result set of a query since otherwise mysql_next_result() would already fail
  540. er = KCERR_DATABASE_ERROR;
  541. ec_log_err("SQL [%08lu] result failed: %s", m_lpMySQL.thread_id, mysql_error(&m_lpMySQL));
  542. goto exit;
  543. }
  544. if (lppResult)
  545. *lppResult = std::move(lpResult);
  546. exit:
  547. if (er != erSuccess) {
  548. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_SELECTS);
  549. g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL));
  550. }
  551. return er;
  552. }
  553. /**
  554. * Finalize a multi-SQL call
  555. *
  556. * A stored procedure will output a NULL result set at the end of the stored procedure to indicate
  557. * that the results have ended. This must be consumed here, otherwise the database will be left in a bad
  558. * state.
  559. */
  560. ECRESULT ECDatabase::FinalizeMulti(void)
  561. {
  562. DB_RESULT lpResult;
  563. autolock alk(*this);
  564. mysql_next_result(&m_lpMySQL);
  565. lpResult = DB_RESULT(this, mysql_store_result(&m_lpMySQL));
  566. if (lpResult != nullptr) {
  567. ec_log_err("SQL [%08lu] result failed: unexpected results received at end of batch", m_lpMySQL.thread_id);
  568. return KCERR_DATABASE_ERROR;
  569. }
  570. return erSuccess;
  571. }
  572. ECRESULT ECDatabase::DoUpdate(const std::string &strQuery,
  573. unsigned int *lpulAffectedRows)
  574. {
  575. auto er = KDatabase::DoUpdate(strQuery, lpulAffectedRows);
  576. g_lpStatsCollector->Increment(SCN_DATABASE_UPDATES);
  577. if (er != erSuccess) {
  578. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_UPDATES);
  579. g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL));
  580. }
  581. return er;
  582. }
  583. ECRESULT ECDatabase::DoInsert(const std::string &strQuery,
  584. unsigned int *lpulInsertId, unsigned int *lpulAffectedRows)
  585. {
  586. auto er = KDatabase::DoInsert(strQuery, lpulInsertId, lpulAffectedRows);
  587. g_lpStatsCollector->Increment(SCN_DATABASE_INSERTS);
  588. if (er != erSuccess) {
  589. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_INSERTS);
  590. g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL));
  591. }
  592. return er;
  593. }
  594. ECRESULT ECDatabase::DoDelete(const std::string &strQuery,
  595. unsigned int *lpulAffectedRows)
  596. {
  597. auto er = KDatabase::DoDelete(strQuery, lpulAffectedRows);
  598. g_lpStatsCollector->Increment(SCN_DATABASE_DELETES);
  599. if (er != erSuccess) {
  600. g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_DELETES);
  601. g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL));
  602. }
  603. return er;
  604. }
  605. /*
  606. */
  607. ECRESULT ECDatabase::DoSequence(const std::string &strSeqName,
  608. unsigned int ulCount, unsigned long long *lpllFirstId)
  609. {
  610. #ifdef DEBUG
  611. #if DEBUG_TRANSACTION
  612. if (m_ulTransactionState != 0)
  613. assert(false);
  614. #endif
  615. #endif
  616. return KDatabase::DoSequence(strSeqName, ulCount, lpllFirstId);
  617. }
  618. /**
  619. * For some reason, MySQL only supports up to 3 bytes of UTF-8 data. This means
  620. * that data outside the BMP is not supported. This function filters the passed UTF-8 string
  621. * and removes the non-BMP characters. Since it should be extremely uncommon to have useful
  622. * data outside the BMP, this should be acceptable.
  623. *
  624. * Note: BMP stands for Basic Multilingual Plane (first 0x10000 code points in unicode)
  625. *
  626. * If somebody points out a useful use case for non-BMP characters in the future, then we'll
  627. * have to rethink this.
  628. *
  629. */
  630. std::string ECDatabase::FilterBMP(const std::string &strToFilter)
  631. {
  632. const char *c = strToFilter.c_str();
  633. std::string::size_type pos = 0;
  634. std::string strFiltered;
  635. while(pos < strToFilter.size()) {
  636. // Copy 1, 2, and 3-byte UTF-8 sequences
  637. int len;
  638. if((c[pos] & 0x80) == 0)
  639. len = 1;
  640. else if((c[pos] & 0xE0) == 0xC0)
  641. len = 2;
  642. else if((c[pos] & 0xF0) == 0xE0)
  643. len = 3;
  644. else if((c[pos] & 0xF8) == 0xF0)
  645. len = 4;
  646. else if((c[pos] & 0xFC) == 0xF8)
  647. len = 5;
  648. else if((c[pos] & 0xFE) == 0xFC)
  649. len = 6;
  650. else
  651. // Invalid UTF-8 ?
  652. len = 1;
  653. if (len < 4)
  654. strFiltered.append(&c[pos], len);
  655. pos += len;
  656. }
  657. return strFiltered;
  658. }
  659. bool ECDatabase::SuppressLockErrorLogging(bool bSuppress)
  660. {
  661. std::swap(bSuppress, m_bSuppressLockErrorLogging);
  662. return bSuppress;
  663. }
  664. ECRESULT ECDatabase::Begin(void)
  665. {
  666. Query("SET autocommit=0;");
  667. auto er = KDatabase::Begin();
  668. #ifdef DEBUG
  669. #if DEBUG_TRANSACTION
  670. ec_log_debug("%08X: BEGIN", &m_lpMySQL);
  671. if(m_ulTransactionState != 0) {
  672. ec_log_debug("BEGIN ALREADY ISSUED");
  673. assert(("BEGIN ALREADY ISSUED", false));
  674. }
  675. m_ulTransactionState = 1;
  676. #endif
  677. #endif
  678. return er;
  679. }
  680. ECRESULT ECDatabase::Commit(void)
  681. {
  682. auto er = KDatabase::Commit();
  683. Query("SET autocommit=1;");
  684. #ifdef DEBUG
  685. #if DEBUG_TRANSACTION
  686. ec_log_debug("%08X: COMMIT", &m_lpMySQL);
  687. if(m_ulTransactionState != 1) {
  688. ec_log_debug("NO BEGIN ISSUED");
  689. assert(("NO BEGIN ISSUED", false));
  690. }
  691. m_ulTransactionState = 0;
  692. #endif
  693. #endif
  694. return er;
  695. }
  696. ECRESULT ECDatabase::Rollback(void)
  697. {
  698. auto er = KDatabase::Rollback();
  699. Query("SET autocommit=1;");
  700. #ifdef DEBUG
  701. #if DEBUG_TRANSACTION
  702. ec_log_debug("%08X: ROLLBACK", &m_lpMySQL);
  703. if(m_ulTransactionState != 1) {
  704. ec_log_debug("NO BEGIN ISSUED");
  705. assert(("NO BEGIN ISSUED", false));
  706. }
  707. m_ulTransactionState = 0;
  708. #endif
  709. #endif
  710. return er;
  711. }
  712. void ECDatabase::ThreadInit(void)
  713. {
  714. mysql_thread_init();
  715. }
  716. void ECDatabase::ThreadEnd(void)
  717. {
  718. mysql_thread_end();
  719. }
  720. ECRESULT ECDatabase::CreateDatabase(void)
  721. {
  722. auto er = KDatabase::CreateDatabase(m_lpConfig, false);
  723. if (er != erSuccess)
  724. return er;
  725. // database default data
  726. static constexpr const sSQLDatabase_t sDatabaseData[] = {
  727. {"users", Z_TABLEDATA_USERS},
  728. {"stores", Z_TABLEDATA_STORES},
  729. {"hierarchy", Z_TABLEDATA_HIERARCHY},
  730. {"properties", Z_TABLEDATA_PROPERTIES},
  731. {"acl", Z_TABLEDATA_ACL},
  732. {"settings", Z_TABLEDATA_SETTINGS},
  733. {"indexedproperties", Z_TABLEDATA_INDEXED_PROPERTIES},
  734. };
  735. // Add the default table data
  736. for (size_t i = 0; i < ARRAY_SIZE(sDatabaseData); ++i) {
  737. ec_log_info("Add table data for \"%s\"", sDatabaseData[i].lpComment);
  738. er = DoInsert(sDatabaseData[i].lpSQL);
  739. if(er != erSuccess)
  740. return er;
  741. }
  742. er = InsertServerGUID(this);
  743. if(er != erSuccess)
  744. return er;
  745. // Add the release id in the database
  746. er = UpdateDatabaseVersion(Z_UPDATE_RELEASE_ID);
  747. if(er != erSuccess)
  748. return er;
  749. // Loop throught the update list
  750. for (size_t i = Z_UPDATE_RELEASE_ID;
  751. i < ARRAY_SIZE(sUpdateList); ++i)
  752. {
  753. er = UpdateDatabaseVersion(sUpdateList[i].ulVersion);
  754. if(er != erSuccess)
  755. return er;
  756. }
  757. ec_log_notice("Database has been created/updated and populated");
  758. return erSuccess;
  759. }
  760. static inline bool row_has_null(DB_ROW row, size_t z)
  761. {
  762. if (row == NULL)
  763. return true;
  764. while (z-- > 0)
  765. if (row[z] == NULL)
  766. return true;
  767. return false;
  768. }
  769. ECRESULT ECDatabase::GetDatabaseVersion(zcp_versiontuple *dbv)
  770. {
  771. ECRESULT er = erSuccess;
  772. string strQuery;
  773. DB_RESULT lpResult;
  774. DB_ROW lpDBRow = NULL;
  775. bool have_micro;
  776. /* Check if the "micro" column already exists (it does since v64) */
  777. er = DoSelect("SELECT databaserevision FROM versions WHERE databaserevision>=64 LIMIT 1", &lpResult);
  778. if (er != erSuccess)
  779. return er;
  780. have_micro = GetNumRows(lpResult) > 0;
  781. strQuery = "SELECT major, minor";
  782. strQuery += have_micro ? ", micro" : ", 0";
  783. strQuery += ", revision, databaserevision FROM versions ORDER BY major DESC, minor DESC";
  784. if (have_micro)
  785. strQuery += ", micro DESC";
  786. strQuery += ", revision DESC, databaserevision DESC LIMIT 1";
  787. er = DoSelect(strQuery, &lpResult);
  788. if(er != erSuccess && mysql_errno(&m_lpMySQL) != ER_NO_SUCH_TABLE)
  789. return er;
  790. if(er != erSuccess || GetNumRows(lpResult) == 0) {
  791. // Ok, maybe < than version 5.10
  792. // check version
  793. strQuery = "SHOW COLUMNS FROM properties";
  794. er = DoSelect(strQuery, &lpResult);
  795. if(er != erSuccess)
  796. return er;
  797. lpDBRow = FetchRow(lpResult);
  798. while (lpDBRow != NULL) {
  799. if (lpDBRow[0] != NULL && strcasecmp(lpDBRow[0], "storeid") == 0) {
  800. dbv->v_major = 5;
  801. dbv->v_minor = 0;
  802. dbv->v_rev = 0;
  803. dbv->v_schema = 0;
  804. er = erSuccess;
  805. break;
  806. }
  807. lpDBRow = FetchRow(lpResult);
  808. }
  809. return KCERR_UNKNOWN_DATABASE;
  810. }
  811. lpDBRow = FetchRow(lpResult);
  812. if (row_has_null(lpDBRow, 5)) {
  813. ec_log_err("ECDatabase::GetDatabaseVersion(): NULL row or columns");
  814. return KCERR_DATABASE_ERROR;
  815. }
  816. dbv->v_major = strtoul(lpDBRow[0], NULL, 0);
  817. dbv->v_minor = strtoul(lpDBRow[1], NULL, 0);
  818. dbv->v_micro = strtoul(lpDBRow[2], NULL, 0);
  819. dbv->v_rev = strtoul(lpDBRow[3], NULL, 0);
  820. dbv->v_schema = strtoul(lpDBRow[4], NULL, 0);
  821. return erSuccess;
  822. }
  823. ECRESULT ECDatabase::IsUpdateDone(unsigned int ulDatabaseRevision,
  824. unsigned int ulRevision)
  825. {
  826. ECRESULT er = KCERR_NOT_FOUND;
  827. string strQuery;
  828. DB_RESULT lpResult;
  829. strQuery = "SELECT major,minor,revision,databaserevision FROM versions WHERE databaserevision = " + stringify(ulDatabaseRevision);
  830. if (ulRevision > 0)
  831. strQuery += " AND revision = " + stringify(ulRevision);
  832. strQuery += " ORDER BY major DESC, minor DESC, revision DESC, databaserevision DESC LIMIT 1";
  833. er = DoSelect(strQuery, &lpResult);
  834. if(er != erSuccess)
  835. return er;
  836. if(GetNumRows(lpResult) != 1)
  837. return KCERR_NOT_FOUND;
  838. return erSuccess;
  839. }
  840. ECRESULT ECDatabase::GetFirstUpdate(unsigned int *lpulDatabaseRevision)
  841. {
  842. ECRESULT er = erSuccess;
  843. DB_RESULT lpResult;
  844. DB_ROW lpDBRow = NULL;
  845. er = DoSelect("SELECT MIN(databaserevision) FROM versions", &lpResult);
  846. if(er != erSuccess && mysql_errno(&m_lpMySQL) != ER_NO_SUCH_TABLE)
  847. return er;
  848. else if(er == erSuccess)
  849. lpDBRow = FetchRow(lpResult);
  850. if (lpDBRow == nullptr || lpDBRow[0] == nullptr)
  851. *lpulDatabaseRevision = 0;
  852. else
  853. *lpulDatabaseRevision = atoui(lpDBRow[0]);
  854. return erSuccess;
  855. }
  856. /**
  857. * Update the database to the current version.
  858. *
  859. * @param[in] bForceUpdate possebly force upgrade
  860. * @param[out] strReport error message
  861. *
  862. * @return Kopano error code
  863. */
  864. ECRESULT ECDatabase::UpdateDatabase(bool bForceUpdate, std::string &strReport)
  865. {
  866. ECRESULT er;
  867. bool bUpdated = false;
  868. bool bSkipped = false;
  869. unsigned int ulDatabaseRevisionMin = 0;
  870. zcp_versiontuple stored_ver;
  871. zcp_versiontuple program_ver(PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR, PROJECT_VERSION_MICRO, PROJECT_VERSION_REVISION, Z_UPDATE_LAST);
  872. int cmp;
  873. er = GetDatabaseVersion(&stored_ver);
  874. if(er != erSuccess)
  875. return er;
  876. er = GetFirstUpdate(&ulDatabaseRevisionMin);
  877. if(er != erSuccess)
  878. return er;
  879. //default error
  880. strReport = "Unable to upgrade database from version " +
  881. stored_ver.stringify() + " to " + program_ver.stringify();
  882. // Check version
  883. cmp = stored_ver.compare(program_ver);
  884. if (cmp == 0 && stored_ver.v_schema == Z_UPDATE_LAST) {
  885. // up to date
  886. return erSuccess;
  887. } else if (cmp > 0) {
  888. // Start a old server with a new database
  889. strReport = "Database version (" + stored_ver.stringify(',') +
  890. ") is newer than the server version (" + program_ver.stringify(',') + ")";
  891. return KCERR_INVALID_VERSION;
  892. }
  893. this->m_bForceUpdate = bForceUpdate;
  894. if (bForceUpdate)
  895. ec_log_warn("Manually forced the database upgrade because the option \"--force-database-upgrade\" was given.");
  896. // Loop throught the update list
  897. for (size_t i = ulDatabaseRevisionMin;
  898. i < ARRAY_SIZE(sUpdateList); ++i)
  899. {
  900. if ((ulDatabaseRevisionMin > 0 && IsUpdateDone(sUpdateList[i].ulVersion) == hrSuccess) ||
  901. (sUpdateList[i].ulVersionMin != 0 && stored_ver.v_schema >= sUpdateList[i].ulVersion &&
  902. stored_ver.v_schema >= sUpdateList[i].ulVersionMin) ||
  903. (sUpdateList[i].ulVersionMin != 0 && IsUpdateDone(sUpdateList[i].ulVersionMin, PROJECT_VERSION_REVISION) == hrSuccess))
  904. // Update already done, next
  905. continue;
  906. ec_log_info("Start: %s", sUpdateList[i].lpszLogComment);
  907. er = Begin();
  908. if(er != erSuccess)
  909. return er;
  910. bSkipped = false;
  911. er = sUpdateList[i].lpFunction(this);
  912. if (er == KCERR_IGNORE_ME) {
  913. bSkipped = true;
  914. er = erSuccess;
  915. } else if (er == KCERR_USER_CANCEL) {
  916. return er; // Reason should be logged in the update itself.
  917. } else if (er != hrSuccess) {
  918. Rollback();
  919. ec_log_err("Failed: Rollback database");
  920. return er;
  921. }
  922. er = UpdateDatabaseVersion(sUpdateList[i].ulVersion);
  923. if(er != erSuccess)
  924. return er;
  925. er = Commit();
  926. if(er != erSuccess)
  927. return er;
  928. ec_log_notice("%s: %s", bSkipped ? "Skipped" : "Done", sUpdateList[i].lpszLogComment);
  929. bUpdated = true;
  930. }
  931. // Ok, no changes for the database, but for update history we add a version record
  932. if(bUpdated == false) {
  933. // Update version table
  934. er = UpdateDatabaseVersion(Z_UPDATE_LAST);
  935. if(er != erSuccess)
  936. return er;
  937. }
  938. return erSuccess;
  939. }
  940. ECRESULT ECDatabase::UpdateDatabaseVersion(unsigned int ulDatabaseRevision)
  941. {
  942. ECRESULT er;
  943. string strQuery;
  944. DB_RESULT result;
  945. bool have_micro;
  946. /* Check for "micro" column (present in v64+) */
  947. er = DoSelect("SELECT databaserevision FROM versions WHERE databaserevision>=64 LIMIT 1", &result);
  948. if (er != erSuccess)
  949. return er;
  950. have_micro = GetNumRows(result) > 0;
  951. // Insert version number
  952. strQuery = "INSERT INTO versions (major, minor, ";
  953. if (have_micro)
  954. strQuery += "micro, ";
  955. strQuery += "revision, databaserevision, updatetime) VALUES(";
  956. strQuery += stringify(PROJECT_VERSION_MAJOR) + std::string(", ") + stringify(PROJECT_VERSION_MINOR) + std::string(", ");
  957. if (have_micro)
  958. strQuery += stringify(PROJECT_VERSION_MICRO) + std::string(", ");
  959. strQuery += std::string("'") + std::string(PROJECT_SVN_REV_STR) + std::string("', ") + stringify(ulDatabaseRevision) + ", FROM_UNIXTIME("+stringify(time(NULL))+") )";
  960. return DoInsert(strQuery);
  961. }
  962. /**
  963. * Validate all database tables
  964. */
  965. ECRESULT ECDatabase::ValidateTables(void)
  966. {
  967. ECRESULT er = erSuccess;
  968. string strQuery;
  969. list<std::string> listTables;
  970. list<std::string> listErrorTables;
  971. DB_RESULT lpResult;
  972. DB_ROW lpDBRow = NULL;
  973. er = DoSelect("SHOW TABLES", &lpResult);
  974. if(er != erSuccess) {
  975. ec_log_err("Unable to get all tables from the mysql database. %s", GetError());
  976. return er;
  977. }
  978. // Get all tables of the database
  979. while( (lpDBRow = FetchRow(lpResult))) {
  980. if (lpDBRow == NULL || lpDBRow[0] == NULL) {
  981. ec_log_err("Wrong table information.");
  982. return KCERR_DATABASE_ERROR;
  983. }
  984. listTables.insert(listTables.end(), lpDBRow[0]);
  985. }
  986. for (const auto &table : listTables) {
  987. er = DoSelect("CHECK TABLE " + table, &lpResult);
  988. if(er != erSuccess) {
  989. ec_log_err("Unable to check table \"%s\"", table.c_str());
  990. return er;
  991. }
  992. lpDBRow = FetchRow(lpResult);
  993. if (lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL) {
  994. ec_log_err("Wrong check table information.");
  995. return KCERR_DATABASE_ERROR;
  996. }
  997. ec_log_info("%30s | %15s | %s", lpDBRow[0], lpDBRow[2], lpDBRow[3]);
  998. if (strcmp(lpDBRow[2], "error") == 0)
  999. listErrorTables.insert(listErrorTables.end(), lpDBRow[0]);
  1000. }
  1001. if (!listErrorTables.empty())
  1002. {
  1003. ec_log_notice("Rebuilding tables.");
  1004. for (const auto &table : listErrorTables) {
  1005. er = DoUpdate("ALTER TABLE " + table + " FORCE");
  1006. if(er != erSuccess) {
  1007. ec_log_crit("Unable to fix table \"%s\"", table.c_str());
  1008. break;
  1009. }
  1010. }
  1011. if (er != erSuccess)
  1012. ec_log_crit("Rebuild tables failed. Error code 0x%08x", er);
  1013. else
  1014. ec_log_notice("Rebuilding tables done.");
  1015. }// if (!listErrorTables.empty())
  1016. return er;
  1017. }
  1018. static constexpr const sSQLDatabase_t kcsrv_tables[] = {
  1019. {"acl", Z_TABLEDEF_ACL},
  1020. {"hierarchy", Z_TABLEDEF_HIERARCHY},
  1021. {"names", Z_TABLEDEF_NAMES},
  1022. {"mvproperties", Z_TABLEDEF_MVPROPERTIES},
  1023. {"tproperties", Z_TABLEDEF_TPROPERTIES},
  1024. {"properties", Z_TABLEDEF_PROPERTIES},
  1025. {"delayedupdate", Z_TABLEDEF_DELAYEDUPDATE},
  1026. {"receivefolder", Z_TABLEDEF_RECEIVEFOLDER},
  1027. {"stores", Z_TABLEDEF_STORES},
  1028. {"users", Z_TABLEDEF_USERS},
  1029. {"outgoingqueue", Z_TABLEDEF_OUTGOINGQUEUE},
  1030. {"lob", Z_TABLEDEF_LOB},
  1031. {"searchresults", Z_TABLEDEF_SEARCHRESULTS},
  1032. {"changes", Z_TABLEDEF_CHANGES},
  1033. {"syncs", Z_TABLEDEF_SYNCS},
  1034. {"versions", Z_TABLEDEF_VERSIONS},
  1035. {"indexedproperties", Z_TABLEDEF_INDEXED_PROPERTIES},
  1036. {"settings", Z_TABLEDEF_SETTINGS},
  1037. {"object", Z_TABLEDEF_OBJECT},
  1038. {"objectproperty", Z_TABLEDEF_OBJECT_PROPERTY},
  1039. {"objectmvproperty", Z_TABLEDEF_OBJECT_MVPROPERTY},
  1040. {"objectrelation", Z_TABLEDEF_OBJECT_RELATION},
  1041. {"singleinstances", Z_TABLEDEF_REFERENCES},
  1042. {"abchanges", Z_TABLEDEF_ABCHANGES},
  1043. {"syncedmessages", Z_TABLEDEFS_SYNCEDMESSAGES},
  1044. {"clientupdatestatus", Z_TABLEDEF_CLIENTUPDATESTATUS},
  1045. {nullptr, nullptr},
  1046. };
  1047. const struct sSQLDatabase_t *ECDatabase::GetDatabaseDefs(void)
  1048. {
  1049. return kcsrv_tables;
  1050. }
  1051. } /* namespace */