diff options
-rw-r--r-- | .SRCINFO | 12 | ||||
-rw-r--r-- | ECDBDef.h | 429 | ||||
-rw-r--r-- | ECDatabaseMySQL.cpp | 2031 | ||||
-rw-r--r-- | ECDatabaseUpdate.cpp | 2719 | ||||
-rw-r--r-- | ECDatabaseUpdate.h | 109 | ||||
-rw-r--r-- | PKGBUILD | 16 |
6 files changed, 5312 insertions, 4 deletions
@@ -1,9 +1,9 @@ # Generated by mksrcinfo v8 -# Tue Dec 20 15:11:06 UTC 2016 +# Tue Dec 20 21:57:46 UTC 2016 pkgbase = zarafa-server pkgdesc = Open Source Groupware Solution pkgver = 7.2.4.29 - pkgrel = 112 + pkgrel = 113 url = http://www.zarafa.com/ install = install arch = armv7h @@ -94,11 +94,19 @@ pkgbase = zarafa-server source = python-zarafa::git+https://github.com/zarafagroupware/python-zarafa.git source = zarafa-inspector::git+https://github.com/zarafagroupware/zarafa-inspector.git source = zarafa-pietma::git+https://git.pietma.com/pietma/com-pietma-zarafa.git#tag=v0.13 + source = ECDBDef.h + source = ECDatabaseMySQL.cpp + source = ECDatabaseUpdate.h + source = ECDatabaseUpdate.cpp md5sums = 0790d8314fa4aef9788e5020be832535 md5sums = SKIP md5sums = SKIP md5sums = SKIP md5sums = SKIP + md5sums = SKIP + md5sums = SKIP + md5sums = SKIP + md5sums = SKIP pkgname = zarafa-server diff --git a/ECDBDef.h b/ECDBDef.h new file mode 100644 index 000000000000..f7c499edf7eb --- /dev/null +++ b/ECDBDef.h @@ -0,0 +1,429 @@ +/* + * Copyright 2005 - 2016 Zarafa and its licensors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef ECDBDEF_H +#define ECDBDEF_H + +/** + * @page kopano_db Database + * + * @section kopano_db_layout Database layout + * + * Server tables: + * @code + * abchanges | All addressbook changes + * acl | User permission objects + * changes | Object changes + * clientupdatestatus| Update status of the kopano client, only used with auto updater + * hierarchy | The hiearchy between the mapi objects + * indexedproperties | Mapi object entryid and sourcekey + * lob | Attachment data. Only when the setting attachment in database is enabled + * mvproperties | The multi value properties of a mapi object. Store, folder and message + * names | Custom property defines + * outgoingqueue | Pending messages to send + * properties | The single value properties of a mapi object. Store, folder and message + * receivefolder | Specifies the mapi receivefolder, for example the inbox + * searchresults | Search folder results + * settings | Server dependent settings + * singleinstances | The relation between an attachment and one or more message objects + * stores | A list with data stores related to one user and includes the deleted stores. + * syncedmessages | Messages which are synced with a specific restriction + * syncs | Sync state of a folder + * users | User relation between the userplugin and kopano + * versions | Database update information + * @endcode + * + * Database and unix user plugin tables: + * @code + * object | Unique user object id and user type + * objectmvproperty | Multi value properties of a user + * objectproperty | Single value properties of a user + * objectrelation | User, group, company and sendas relations + * @endcode + * + * @todo Add an image of the database layout + * + * @section kopano_db_update Database update system + * + * @todo describe the update system + * + * + */ + +#define Z_TABLEDEF_ACL "CREATE TABLE `acl` ( \ + `id` int(11) NOT NULL default '0', \ + `hierarchy_id` int(11) unsigned NOT NULL default '0', \ + `type` tinyint(4) unsigned NOT NULL default '0', \ + `rights` int(11) unsigned NOT NULL default '0', \ + PRIMARY KEY (`hierarchy_id`,`id`,`type`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_HIERARCHY "CREATE TABLE `hierarchy` ( \ + `id` int(11) unsigned NOT NULL auto_increment, \ + `parent` int(11) unsigned default '0', \ + `type` tinyint(4) unsigned NOT NULL default '0', \ + `flags` smallint(6) unsigned NOT NULL default '0', \ + `owner` int(11) unsigned NOT NULL default '0', \ + PRIMARY KEY (`id`), \ + KEY `parenttypeflags` (`parent`, `type`, `flags`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_NAMES "CREATE TABLE `names` ( \ + `id` int(11) NOT NULL auto_increment, \ + `nameid` int(11) default NULL, \ + `namestring` varchar(255) binary default NULL, \ + `guid` blob NOT NULL, \ + PRIMARY KEY (`id`), \ + KEY `nameid` (`nameid`), \ + KEY `namestring` (`namestring`), \ + KEY `guidnameid` (`guid`(16),`nameid`), \ + KEY `guidnamestring` (`guid`(16),`namestring`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_MVPROPERTIES "CREATE TABLE `mvproperties` ( \ + `hierarchyid` int(11) unsigned NOT NULL default '0', \ + `orderid` smallint(6) unsigned NOT NULL default '0', \ + `tag` smallint(6) unsigned NOT NULL default '0', \ + `type` smallint(6) unsigned NOT NULL default '0', \ + `val_ulong` int(11) unsigned default NULL, \ + `val_string` longtext, \ + `val_binary` longblob, \ + `val_double` double default NULL, \ + `val_longint` bigint(20) default NULL, \ + `val_hi` int(11) default NULL, \ + `val_lo` int(11) unsigned default NULL, \ + PRIMARY KEY (`hierarchyid`, `tag`, `type`, `orderid`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_TPROPERTIES "CREATE TABLE `tproperties` ( \ + `folderid` int(11) unsigned NOT NULL default '0', \ + `hierarchyid` int(11) unsigned NOT NULL default '0', \ + `tag` smallint(6) unsigned NOT NULL default '0', \ + `type` smallint(6) unsigned NOT NULL, \ + `val_ulong` int(11) unsigned default NULL, \ + `val_string` longtext, \ + `val_binary` longblob, \ + `val_double` double default NULL, \ + `val_longint` bigint(20) default NULL, \ + `val_hi` int(11) default NULL, \ + `val_lo` int(11) unsigned default NULL, \ + PRIMARY KEY `ht` (`folderid`,`tag`,`hierarchyid`,`type`), \ + KEY `hi` (`hierarchyid`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_DELAYEDUPDATE "CREATE TABLE `deferredupdate` (\ + `hierarchyid` int(11) unsigned NOT NULL, \ + `folderid` int(11) unsigned NOT NULL, \ + `srcfolderid` int(11) unsigned, \ + PRIMARY KEY(`hierarchyid`), \ + KEY `folderid` (`folderid`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_PROPERTIES "CREATE TABLE `properties` ( \ + `hierarchyid` int(11) unsigned NOT NULL default '0', \ + `tag` smallint(6) unsigned NOT NULL default '0', \ + `type` smallint(6) unsigned NOT NULL, \ + `val_ulong` int(11) unsigned default NULL, \ + `val_string` longtext, \ + `val_binary` longblob, \ + `val_double` double default NULL, \ + `val_longint` bigint(20) default NULL, \ + `val_hi` int(11) default NULL, \ + `val_lo` int(11) unsigned default NULL, \ + `comp` bool default false, \ + PRIMARY KEY `ht` (`hierarchyid`,`tag`,`type`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_RECEIVEFOLDER "CREATE TABLE `receivefolder` ( \ + `id` int(11) unsigned NOT NULL auto_increment, \ + `storeid` int(11) unsigned NOT NULL default '0', \ + `objid` int(11) unsigned NOT NULL default '0', \ + `messageclass` varchar(255) NOT NULL default '', \ + PRIMARY KEY (`id`), \ + UNIQUE KEY `storeid` (`storeid`,`messageclass`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_STORES "CREATE TABLE `stores` ( \ + `id` int(11) unsigned NOT NULL auto_increment, \ + `hierarchy_id` int(11) unsigned NOT NULL default '0', \ + `user_id` int(11) unsigned NOT NULL default '0', \ + `type` smallint(6) unsigned NOT NULL default '0', \ + `user_name` varchar(255) CHARACTER SET utf8 NOT NULL default '', \ + `company` int(11) unsigned NOT NULL default '0', \ + `guid` blob NOT NULL, \ + PRIMARY KEY (`user_id`, `hierarchy_id`, `type`), \ + UNIQUE KEY `id` (`id`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_USERS "CREATE TABLE `users` ( \ + `id` int(11) unsigned NOT NULL auto_increment, \ + `externid` blob, \ + `objectclass` int(11) NOT NULL default '0', \ + `signature` varbinary(255) NOT NULL default '0', \ + `company` int(11) unsigned NOT NULL default '0', \ + PRIMARY KEY (`id`), \ + UNIQUE KEY externid (`externid`(255), `objectclass`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_OUTGOINGQUEUE "CREATE TABLE `outgoingqueue` ( \ + `store_id` int(11) unsigned NOT NULL default '0', \ + `hierarchy_id` int(11) unsigned NOT NULL default '0', \ + `flags` tinyint(4) unsigned NOT NULL default '0', \ + PRIMARY KEY (`hierarchy_id`,`flags`,`store_id`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_LOB "CREATE TABLE `lob` ( \ + `instanceid` int(11) unsigned NOT NULL, \ + `chunkid` smallint(6) unsigned NOT NULL, \ + `tag` smallint(6) unsigned NOT NULL, \ + `val_binary` longblob, \ + PRIMARY KEY (`instanceid`,`tag`,`chunkid`) \ + ) ENGINE=InnoDB MAX_ROWS=1000000000 AVG_ROW_LENGTH=1750 CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_REFERENCES "CREATE TABLE `singleinstances` ( \ + `instanceid` int(11) unsigned NOT NULL auto_increment, \ + `hierarchyid` int(11) unsigned NOT NULL default '0', \ + `tag` smallint(6) unsigned NOT NULL default '0', \ + PRIMARY KEY (`instanceid`, `hierarchyid`, `tag`), \ + UNIQUE KEY `hkey` (`hierarchyid`, `tag`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_OBJECT "CREATE TABLE object ( \ + `id` int(11) unsigned NOT NULL auto_increment, \ + `externid` blob, \ + `objectclass` int(11) unsigned NOT NULL default '0', \ + PRIMARY KEY (`id`, `objectclass`), \ + UNIQUE KEY id (`id`), \ + UNIQUE KEY externid (`externid`(255), `objectclass`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_OBJECT_PROPERTY "CREATE TABLE objectproperty ( \ + `objectid` int(11) unsigned NOT NULL default '0', \ + `propname` varchar(255) binary NOT NULL, \ + `value` text, \ + PRIMARY KEY (`objectid`, `propname`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_OBJECT_MVPROPERTY "CREATE TABLE objectmvproperty ( \ + `objectid` int(11) unsigned NOT NULL default '0', \ + `propname` varchar(255) binary NOT NULL, \ + `orderid` tinyint(11) unsigned NOT NULL default '0', \ + `value` text, \ + PRIMARY KEY (`objectid`, `orderid`, `propname`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_OBJECT_RELATION "CREATE TABLE objectrelation ( \ + `objectid` int(11) unsigned NOT NULL default '0', \ + `parentobjectid` int(11) unsigned NOT NULL default '0', \ + `relationtype` tinyint(11) unsigned NOT NULL, \ + PRIMARY KEY (`objectid`, `parentobjectid`, `relationtype`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_VERSIONS "CREATE TABLE versions ( \ + `major` int(11) unsigned NOT NULL default '0', \ + `minor` int(11) unsigned NOT NULL default '0', \ + `micro` int(11) unsigned not null default 0, \ + `revision` int(11) unsigned NOT NULL default '0', \ + `databaserevision` int(11) unsigned NOT NULL default '0', \ + `updatetime` datetime NOT NULL, \ + PRIMARY KEY (`major`, `minor`, `micro`, `revision`, `databaserevision`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_SEARCHRESULTS "CREATE TABLE searchresults ( \ + `folderid` int(11) unsigned NOT NULL default '0', \ + `hierarchyid` int(11) unsigned NOT NULL default '0', \ + `flags` int(11) unsigned NOT NULL default '0', \ + PRIMARY KEY (`folderid`, `hierarchyid`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_CHANGES "CREATE TABLE `changes` ( \ + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, \ + `sourcekey` VARBINARY(64) NOT NULL, \ + `parentsourcekey` VARBINARY(64) NOT NULL, \ + `change_type` INT(11) UNSIGNED NOT NULL DEFAULT '0', \ + `flags` INT(11) UNSIGNED DEFAULT NULL, \ + `sourcesync` INT(11) UNSIGNED DEFAULT NULL, \ + PRIMARY KEY (`parentsourcekey`,`sourcekey`,`change_type`), \ + UNIQUE KEY `changeid` (`id`), \ + UNIQUE KEY `state` (`parentsourcekey`,`id`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_ABCHANGES "CREATE TABLE `abchanges` ( \ + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, \ + `sourcekey` VARBINARY(255) NOT NULL, \ + `parentsourcekey` VARBINARY(255) NOT NULL, \ + `change_type` INT(11) UNSIGNED NOT NULL DEFAULT '0', \ + PRIMARY KEY (`parentsourcekey`,`change_type`,`sourcekey`), \ + UNIQUE KEY `changeid` (`id`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_SYNCS "CREATE TABLE `syncs` ( \ + `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, \ + `sourcekey` VARBINARY(64) NOT NULL, \ + `change_id` INT(11) UNSIGNED NOT NULL, \ + `sync_type` INT(11) UNSIGNED NULL, \ + `sync_time` DATETIME NOT NULL, \ + PRIMARY KEY(`id`), \ + KEY `foldersync` (`sourcekey`,`sync_type`), \ + KEY `changes` (`change_id`), \ + KEY `sync_time` (`sync_time`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEFS_SYNCEDMESSAGES "CREATE TABLE `syncedmessages` ( \ + `sync_id` int(11) unsigned NOT NULL, \ + `change_id` int(11) unsigned NOT NULL, \ + `sourcekey` varbinary(64) NOT NULL, \ + `parentsourcekey` varbinary(64) NOT NULL, \ + PRIMARY KEY (`sync_id`,`change_id`,`sourcekey`), \ + KEY `sync_state` (`sync_id`,`change_id`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_INDEXED_PROPERTIES "CREATE TABLE indexedproperties ( \ + `hierarchyid` int(11) unsigned NOT NULL default '0', \ + `tag` smallint(6) unsigned NOT NULL default '0', \ + `val_binary` varbinary(255), \ + PRIMARY KEY (`hierarchyid`, `tag`), \ + UNIQUE KEY `bin` (`tag`, `val_binary`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_SETTINGS "CREATE TABLE settings ( \ + `name` varchar(255) binary NOT NULL, \ + `value` blob NOT NULL, \ + PRIMARY KEY (`name`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +#define Z_TABLEDEF_CLIENTUPDATESTATUS "CREATE TABLE clientupdatestatus ( \ + `userid` int(11) unsigned NOT NULL, \ + `trackid` int(11) unsigned NOT NULL, \ + `updatetime` DATETIME NOT NULL, \ + `currentversion` varchar(50) binary NOT NULL, \ + `latestversion` varchar(50) binary NOT NULL, \ + `computername` varchar(255) binary NOT NULL, \ + `status` int(11) unsigned NOT NULL, \ + PRIMARY KEY (`userid`), \ + UNIQUE KEY (`trackid`) \ + ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;" + +// Default mysql table data +#define Z_TABLEDATA_ACL "INSERT INTO `acl` VALUES (2, 2, 2, 1531), \ + (2, 1, 2, 1531), \ + (1, 1, 2, 1531), \ + (1, 2, 2, 1531);" + +#define Z_TABLEDATA_HIERARCHY "INSERT INTO `hierarchy` VALUES \ + (1, NULL, 1, 0, 2),\ + (2, 1, 3, 0, 2);" + +#define Z_TABLEDATA_PROPERTIES "INSERT INTO `properties` VALUES \ + (1, 12289, 30, NULL, 'Admin store', NULL, NULL, NULL, NULL, NULL, false), \ + (2, 12289, 30, NULL, 'root Admin store', NULL, NULL, NULL, NULL, NULL, false);" + +#define Z_TABLEDATA_STORES "INSERT INTO `stores` VALUES (1, 1, 2, 0, 'SYSTEM', 0, 0x8962ffeffb7b4d639bc5967c4bb58234);" + +//1=KOPANO_UID_EVERYONE, 0x30002=DISTLIST_SECURITY +//2=KOPANO_UID_SYSTEM, 0x10001=ACTIVE_USER +#define Z_TABLEDATA_USERS "INSERT INTO `users` (`id`, `externid`, `objectclass`, `signature`, `company`) VALUES \ + (1, NULL, 0x30002, '', 0), \ + (2, NULL, 0x10001, '', 0);" + +#define Z_TABLEDATA_INDEXED_PROPERTIES "INSERT INTO `indexedproperties` VALUES (1, 0x0FFF, 0x000000008962ffeffb7b4d639bc5967c4bb5823400000000010000000100000000000000 ), \ + (2,0x0FFF, 0x000000008962ffeffb7b4d639bc5967c4bb5823400000000030000000200000000000000);" + +#define Z_TABLEDATA_SETTINGS "INSERT INTO `settings` VALUES ('source_key_auto_increment' , CHAR(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)), \ + ('imapseq', '3'), \ + ('charset', 'utf8');" + +// Database update definitions +#define Z_UPDATE_CREATE_VERSIONS_TABLE 1 +#define Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE 2 +#define Z_UPDATE_FIX_USERTABLE_NONACTIVE 3 +#define Z_UPDATE_ADD_FLAGS_TO_SEARCHRESULTS 4 +#define Z_UPDATE_POPULATE_SEARCHFOLDERS 5 +#define Z_UPDATE_CREATE_CHANGES_TABLE 6 +#define Z_UPDATE_CREATE_SYNCS_TABLE 7 +#define Z_UPDATE_CREATE_INDEXEDPROPS_TABLE 8 +#define Z_UPDATE_CREATE_SETTINGS_TABLE 9 +#define Z_UPDATE_CREATE_SERVER_GUID 10 +#define Z_UPDATE_CREATE_SOURCE_KEYS 11 +#define Z_UPDATE_CONVERT_ENTRYIDS 12 +#define Z_UPDATE_CONVERT_SC_ENTRYIDLIST 13 +#define Z_UPDATE_CONVERT_USER_OBJECT_TYPE 14 +#define Z_UPDATE_ADD_USER_SIGNATURE 15 +#define Z_UPDATE_ADD_SOURCE_KEY_SETTING 16 +#define Z_UPDATE_FIX_USERS_RESTRICTIONS 17 +#define Z_UPDATE_ADD_USER_COMPANY 18 +#define Z_UPDATE_ADD_OBJECT_RELATION_TYPE 19 +#define Z_UPDATE_DEL_DEFAULT_COMPANY 20 +#define Z_UPDATE_ADD_COMPANY_TO_STORES 21 +#define Z_UPDATE_ADD_IMAP_SEQ 22 +#define Z_UPDATE_KEYS_CHANGES 23 +#define Z_UPDATE_MOVE_PUBLICFOLDERS 24 +#define Z_UPDATE_ADD_EXTERNID_TO_OBJECT 25 +#define Z_UPDATE_CREATE_REFERENCES 26 +#define Z_UPDATE_LOCK_DISTRIBUTED 27 +#define Z_UPDATE_CREATE_ABCHANGES_TABLE 28 +#define Z_UPDATE_SINGLEINSTANCE_TAG 29 +#define Z_UPDATE_CREATE_SYNCEDMESSAGES_TABLE 30 +#define Z_UPDATE_FORCE_AB_RESYNC 31 +#define Z_UPDATE_RENAME_OBJECT_TYPE_TO_CLASS 32 +#define Z_UPDATE_CONVERT_OBJECT_TYPE_TO_CLASS 33 +#define Z_UPDATE_ADD_OBJECT_MVPROPERTY_TABLE 34 +#define Z_UPDATE_COMPANYNAME_TO_COMPANYID 35 +#define Z_UPDATE_OUTGOINGQUEUE_PRIMARY_KEY 36 +#define Z_UPDATE_ACL_PRIMARY_KEY 37 +#define Z_UPDATE_BLOB_EXTERNID 38 +#define Z_UPDATE_KEYS_CHANGES_2 39 +#define Z_UPDATE_MVPROPERTIES_PRIMARY_KEY 40 +#define Z_UPDATE_FIX_SECURITYGROUP_DBPLUGIN 41 +#define Z_UPDATE_CONVERT_SENDAS_DBPLUGIN 42 +#define Z_UPDATE_MOVE_IMAP_SUBSCRIBES 43 +#define Z_UPDATE_SYNC_TIME_KEY 44 +#define Z_UPDATE_ADD_STATE_KEY 45 +#define Z_UPDATE_CONVERT_TO_UNICODE 46 +#define Z_UPDATE_CONVERT_STORE_USERNAME 47 +#define Z_UPDATE_CONVERT_RULES 48 +#define Z_UPDATE_CONVERT_SEARCH_FOLDERS 49 +#define Z_UPDATE_CONVERT_PROPERTIES 50 +#define Z_UPDATE_CREATE_COUNTERS 51 +#define Z_UPDATE_CREATE_COMMON_PROPS 52 +#define Z_UPDATE_CHECK_ATTACHMENTS 53 +#define Z_UPDATE_CREATE_TPROPERTIES 54 +#define Z_UPDATE_CONVERT_HIERARCHY 55 +#define Z_UPDATE_CREATE_DEFERRED 56 +#define Z_UPDATE_CONVERT_CHANGES 57 +#define Z_UPDATE_CONVERT_NAMES 58 +#define Z_UPDATE_CONVERT_RF_TOUNICODE 59 +#define Z_UPDATE_CREATE_CLIENTUPDATE_TABLE 60 +#define Z_UPDATE_CONVERT_STORES 61 +#define Z_UPDATE_UPDATE_STORES 62 +#define Z_UPDATE_UPDATE_WLINK_RECKEY 63 +#define Z_UPDATE_VERSIONTBL_MICRO 64 +#define Z_UPDATE_CHANGES_PKEY 65 +#define Z_UPDATE_ABCHANGES_PKEY 66 + +/* + * The first population of the SQL tables can use both create-type and + * update-type operations; %Z_UPDATE_RELEASE_ID specifies the schema + * version that can be reached with creates only. + * (This is never less than %Z_UPDATE_LAST.) + */ +#define Z_UPDATE_RELEASE_ID 66 + +// This is the last update ID always update this to the last ID +#define Z_UPDATE_LAST 66 + +#endif diff --git a/ECDatabaseMySQL.cpp b/ECDatabaseMySQL.cpp new file mode 100644 index 000000000000..d9698c297c7d --- /dev/null +++ b/ECDatabaseMySQL.cpp @@ -0,0 +1,2031 @@ +/* + * Copyright 2005 - 2015 Zarafa B.V. and its licensors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <zarafa/platform.h> + +#include <iostream> +#include <errmsg.h> +#include "ECDatabaseMySQL.h" +#include "mysqld_error.h" + +#include <zarafa/stringutil.h> + +#include <zarafa/ECDefs.h> +#include "ECDBDef.h" +#include "ECUserManagement.h" +#include <zarafa/ECConfig.h> +#include <zarafa/ECLogger.h> +#include <zarafa/MAPIErrors.h> +#include <zarafa/ZarafaCode.h> + +#include <zarafa/ecversion.h> + +#include <mapidefs.h> +#include "ECConversion.h" +#include "SOAPUtils.h" +#include "ECSearchFolders.h" + +#include "ECDatabaseUpdate.h" +#include "ECStatsCollector.h" + +#ifdef HAVE_OFFLINE_SUPPORT +#include "ECDBUpdateProgress.h" +#endif + +using namespace std; + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static const char THIS_FILE[] = __FILE__; +#endif +#ifdef DEBUG +#define DEBUG_SQL 0 +#define DEBUG_TRANSACTION 0 +#endif + +#define LOG_SQL_DEBUG(_msg, ...) \ + ec_log(EC_LOGLEVEL_DEBUG | EC_LOGLEVEL_SQL, _msg, ##__VA_ARGS__) + +// The maximum packet size. This is automatically also the maximum +// size of a single entry in the database. This means that PR_BODY, PR_COMPRESSED_RTF +// etc. cannot grow larger than 16M. This shouldn't be such a problem in practice. + +// In debian lenny, setting your max_allowed_packet to 16M actually gives this value.... Unknown +// why. +#define MAX_ALLOWED_PACKET 16776192 + +typedef struct _sUpdateList { + unsigned int ulVersion; + unsigned int ulVersionMin; // Version to start the update + const char *lpszLogComment; + ECRESULT (*lpFunction)(ECDatabase* lpDatabase); +} sUpdateList_t; + +typedef struct _sNewDatabase { + const char *lpComment; + const char *lpSQL; +} sSQLDatabase_t; + +class zcp_versiontuple _zcp_final { + public: + zcp_versiontuple(unsigned int maj = 0, unsigned int min = 0, + unsigned int mic = 0, unsigned int rev = 0, unsigned int dbs = 0) : + v_major(maj), v_minor(min), v_micro(mic), + v_rev(rev), v_schema(dbs) + { + } + std::string stringify(char sep = '.') const; + int compare(const zcp_versiontuple &) const; + /* stupid major(3) function uses a #define in glibc */ + unsigned int v_major, v_minor, v_micro, v_rev, v_schema; +}; + +static const sUpdateList_t sUpdateList[] = { + // Updates from version 5.02 to 5.10 + { Z_UPDATE_CREATE_VERSIONS_TABLE, 0, "Create table: versions", UpdateDatabaseCreateVersionsTable }, + { Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE, 0, "Create table: searchresults", UpdateDatabaseCreateSearchFolders }, + { Z_UPDATE_FIX_USERTABLE_NONACTIVE, 0, "Update table: users, field: nonactive", UpdateDatabaseFixUserNonActive }, + // Only update if previous revision was after Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE, because the definition has changed + { Z_UPDATE_ADD_FLAGS_TO_SEARCHRESULTS, Z_UPDATE_CREATE_SEARCHFOLDERS_TABLE, "Update table: searchresults", UpdateDatabaseCreateSearchFoldersFlags }, + { Z_UPDATE_POPULATE_SEARCHFOLDERS, 0, "Populate search folders", UpdateDatabasePopulateSearchFolders }, + + // Updates from version 5.10 to 5.20 + { Z_UPDATE_CREATE_CHANGES_TABLE, 0, "Create table: changes", UpdateDatabaseCreateChangesTable }, + { Z_UPDATE_CREATE_SYNCS_TABLE, 0, "Create table: syncs", UpdateDatabaseCreateSyncsTable }, + { Z_UPDATE_CREATE_INDEXEDPROPS_TABLE, 0, "Create table: indexedproperties", UpdateDatabaseCreateIndexedPropertiesTable }, + { Z_UPDATE_CREATE_SETTINGS_TABLE, 0, "Create table: settings", UpdateDatabaseCreateSettingsTable}, + { Z_UPDATE_CREATE_SERVER_GUID, 0, "Insert server GUID into settings", UpdateDatabaseCreateServerGUID }, + { Z_UPDATE_CREATE_SOURCE_KEYS, 0, "Insert source keys into indexedproperties", UpdateDatabaseCreateSourceKeys }, + + // Updates from version 5.20 to 6.00 + { Z_UPDATE_CONVERT_ENTRYIDS, 0, "Convert entryids: indexedproperties", UpdateDatabaseConvertEntryIDs }, + { Z_UPDATE_CONVERT_SC_ENTRYIDLIST, 0, "Update entrylist searchcriteria", UpdateDatabaseSearchCriteria }, + { Z_UPDATE_CONVERT_USER_OBJECT_TYPE, 0, "Add Object type to 'users' table", UpdateDatabaseAddUserObjectType }, + { Z_UPDATE_ADD_USER_SIGNATURE, 0, "Add signature to 'users' table", UpdateDatabaseAddUserSignature }, + { Z_UPDATE_ADD_SOURCE_KEY_SETTING, 0, "Add setting 'source_key_auto_increment'", UpdateDatabaseAddSourceKeySetting }, + { Z_UPDATE_FIX_USERS_RESTRICTIONS, 0, "Add restriction to 'users' table", UpdateDatabaseRestrictExternId }, + + // Update from version 6.00 to 6.10 + { Z_UPDATE_ADD_USER_COMPANY, 0, "Add company column to 'users' table", UpdateDatabaseAddUserCompany }, + { Z_UPDATE_ADD_OBJECT_RELATION_TYPE, 0, "Add Object relation type to 'objectrelation' table", UpdateDatabaseAddObjectRelationType }, + { Z_UPDATE_DEL_DEFAULT_COMPANY, 0, "Delete default company from 'users' table", UpdateDatabaseDelUserCompany}, + { Z_UPDATE_ADD_COMPANY_TO_STORES, 0, "Adding company to 'stores' table", UpdateDatabaseAddCompanyToStore}, + + // Update from version x to x + { Z_UPDATE_ADD_IMAP_SEQ, 0, "Add IMAP sequence number in 'settings' table", UpdateDatabaseAddIMAPSequenceNumber}, + { Z_UPDATE_KEYS_CHANGES, Z_UPDATE_CREATE_CHANGES_TABLE, "Update keys in 'changes' table", UpdateDatabaseKeysChanges}, + + // Update from version 6.1x to 6.20 + { Z_UPDATE_MOVE_PUBLICFOLDERS, 0, "Moving publicfolders and favorites", UpdateDatabaseMoveFoldersInPublicFolder}, + + // Update from version 6.2x to 6.30 + { Z_UPDATE_ADD_EXTERNID_TO_OBJECT, 0, "Adding externid to 'object' table", UpdateDatabaseAddExternIdToObject}, + { Z_UPDATE_CREATE_REFERENCES, 0, "Creating Single Instance Attachment table", UpdateDatabaseCreateReferences}, + { Z_UPDATE_LOCK_DISTRIBUTED, 0, "Locking multiserver capability", UpdateDatabaseLockDistributed}, + { Z_UPDATE_CREATE_ABCHANGES_TABLE, 0, "Creating Addressbook Changes table", UpdateDatabaseCreateABChangesTable}, + { Z_UPDATE_SINGLEINSTANCE_TAG, 0, "Updating 'singleinstances' table to correct tag value", UpdateDatabaseSetSingleinstanceTag}, + + // Update from version 6.3x to 6.40 + { Z_UPDATE_CREATE_SYNCEDMESSAGES_TABLE, 0, "Create table: synced messages", UpdateDatabaseCreateSyncedMessagesTable}, + + // Update from < 6.30 to >= 6.30 + { Z_UPDATE_FORCE_AB_RESYNC, 0, "Force Addressbook Resync", UpdateDatabaseForceAbResync}, + + // Update from version 6.3x to 6.40 + { Z_UPDATE_RENAME_OBJECT_TYPE_TO_CLASS, 0, "Rename objecttype columns to objectclass", UpdateDatabaseRenameObjectTypeToObjectClass}, + { Z_UPDATE_CONVERT_OBJECT_TYPE_TO_CLASS, 0, "Convert objecttype columns to objectclass values", UpdateDatabaseConvertObjectTypeToObjectClass}, + { Z_UPDATE_ADD_OBJECT_MVPROPERTY_TABLE, 0, "Add object MV property table", UpdateDatabaseAddMVPropertyTable}, + { Z_UPDATE_COMPANYNAME_TO_COMPANYID, 0, "Link objects in DB plugin through companyid", UpdateDatabaseCompanyNameToCompanyId}, + { Z_UPDATE_OUTGOINGQUEUE_PRIMARY_KEY, 0, "Update outgoingqueue key", UpdateDatabaseOutgoingQueuePrimarykey}, + { Z_UPDATE_ACL_PRIMARY_KEY, 0, "Update acl key", UpdateDatabaseACLPrimarykey}, + { Z_UPDATE_BLOB_EXTERNID, 0, "Update externid in object table", UpdateDatabaseBlobExternId}, // Avoid MySQL 4.x traling spaces quirk + { Z_UPDATE_KEYS_CHANGES_2, 0, "Update keys in 'changes' table", UpdateDatabaseKeysChanges2}, + { Z_UPDATE_MVPROPERTIES_PRIMARY_KEY, 0, "Update mvproperties key", UpdateDatabaseMVPropertiesPrimarykey}, + { Z_UPDATE_FIX_SECURITYGROUP_DBPLUGIN, 0, "Update DB plugin group to security groups", UpdateDatabaseFixDBPluginGroups}, + { Z_UPDATE_CONVERT_SENDAS_DBPLUGIN, 0, "Update DB/Unix plugin sendas settings", UpdateDatabaseFixDBPluginSendAs}, + + { Z_UPDATE_MOVE_IMAP_SUBSCRIBES, 0, "Move IMAP subscribed list from store to inbox", UpdateDatabaseMoveSubscribedList}, + { Z_UPDATE_SYNC_TIME_KEY, 0, "Update sync table time index", UpdateDatabaseSyncTimeIndex }, + + // Update within the 6.40 + { Z_UPDATE_ADD_STATE_KEY, 0, "Update changes table state key", UpdateDatabaseAddStateKey }, + + // Blocking upgrade from 6.40 to 7.00, tables are not unicode compatible. + { Z_UPDATE_CONVERT_TO_UNICODE, 0, "Converting database to Unicode", UpdateDatabaseConvertToUnicode }, + + // Update from version 6.4x to 7.00 + { Z_UPDATE_CONVERT_STORE_USERNAME, 0, "Update stores table usernames", UpdateDatabaseConvertStoreUsername }, + { Z_UPDATE_CONVERT_RULES, 0, "Converting rules to Unicode", UpdateDatabaseConvertRules }, + { Z_UPDATE_CONVERT_SEARCH_FOLDERS, 0, "Converting search folders to Unicode", UpdateDatabaseConvertSearchFolders }, + { Z_UPDATE_CONVERT_PROPERTIES, 0, "Converting properties for IO performance", UpdateDatabaseConvertProperties }, + { Z_UPDATE_CREATE_COUNTERS, 0, "Creating counters for IO performance", UpdateDatabaseCreateCounters }, + { Z_UPDATE_CREATE_COMMON_PROPS, 0, "Creating common properties for IO performance", UpdateDatabaseCreateCommonProps }, + { Z_UPDATE_CHECK_ATTACHMENTS, 0, "Checking message attachment properties for IO performance", UpdateDatabaseCheckAttachments }, + { Z_UPDATE_CREATE_TPROPERTIES, 0, "Creating tproperties for IO performance", UpdateDatabaseCreateTProperties }, + { Z_UPDATE_CONVERT_HIERARCHY, 0, "Converting hierarchy for IO performance", UpdateDatabaseConvertHierarchy }, + { Z_UPDATE_CREATE_DEFERRED, 0, "Creating deferred table for IO performance", UpdateDatabaseCreateDeferred }, + { Z_UPDATE_CONVERT_CHANGES, 0, "Converting changes for IO performance", UpdateDatabaseConvertChanges }, + { Z_UPDATE_CONVERT_NAMES, 0, "Converting names table to Unicode", UpdateDatabaseConvertNames }, + + // Update from version 7.00 to 7.0.1 + { Z_UPDATE_CONVERT_RF_TOUNICODE, 0, "Converting receivefolder table to Unicode", UpdateDatabaseReceiveFolderToUnicode }, + + // Update from 6.40.13 / 7.0.3 + { Z_UPDATE_CREATE_CLIENTUPDATE_TABLE, 0, "Creating client update status table", UpdateDatabaseClientUpdateStatus }, + + { Z_UPDATE_CONVERT_STORES, 0, "Converting stores table", UpdateDatabaseConvertStores }, + { Z_UPDATE_UPDATE_STORES, 0, "Updating stores table", UpdateDatabaseUpdateStores }, + + // Update from 7.0 to 7.1 + { Z_UPDATE_UPDATE_WLINK_RECKEY, 0, "Updating wunderbar record keys", UpdateWLinkRecordKeys }, + + // New in 7.2.2 + { Z_UPDATE_VERSIONTBL_MICRO, 0, "Add \"micro\" column to \"versions\" table", UpdateVersionsTbl }, + + // New in 8.1.0 / 7.2.4, MySQL 5.7 compatibility + { Z_UPDATE_ABCHANGES_PKEY, 0, "Updating abchanges table", UpdateABChangesTbl }, + { Z_UPDATE_CHANGES_PKEY, 0, "Updating changes table", UpdateChangesTbl }, +}; + +static const char *const server_groups[] = { + "zarafa", + NULL, +}; + +typedef struct { + const char *szName; + const char *szSQL; +} STOREDPROCS; + +/** + * Mode 0 = All bodies + * Mode 1 = Best body only (RTF better than HTML) + plaintext + * Mode 2 = Plaintext only + */ + +static const char szGetProps[] = +"CREATE PROCEDURE GetProps(IN hid integer, IN mode integer)\n" +"BEGIN\n" +" DECLARE bestbody INT;\n" + +" IF mode = 1 THEN\n" +" call GetBestBody(hid, bestbody);\n" +" END IF;\n" + +" 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" +" 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" +" UNION\n" +" SELECT count(*), tag, mvproperties.type, \n" +" group_concat(length(mvproperties.val_ulong),':', mvproperties.val_ulong ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_string),':', mvproperties.val_string ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_binary),':', mvproperties.val_binary ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_double),':', mvproperties.val_double ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_longint),':', mvproperties.val_longint ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_hi),':', mvproperties.val_hi ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_lo),':', mvproperties.val_lo ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" 0, names.nameid, names.namestring, names.guid \n" +" 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" +"END;\n"; + +static const char szPrepareGetProps[] = +"CREATE PROCEDURE PrepareGetProps(IN hid integer)\n" +"BEGIN\n" +" 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" +" 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" +" SELECT count(*), tag, mvproperties.type, \n" +" group_concat(length(mvproperties.val_ulong),':', mvproperties.val_ulong ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_string),':', mvproperties.val_string ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_binary),':', mvproperties.val_binary ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_double),':', mvproperties.val_double ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_longint),':', mvproperties.val_longint ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_hi),':', mvproperties.val_hi ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" group_concat(length(mvproperties.val_lo),':', mvproperties.val_lo ORDER BY mvproperties.orderid SEPARATOR ''), \n" +" hierarchy.id, names.nameid, names.namestring, names.guid \n" +" 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" +"END;\n"; + + +static const char szGetBestBody[] = +"CREATE PROCEDURE GetBestBody(hid integer, OUT bestbody integer)\n" +"DETERMINISTIC\n" +"BEGIN\n" +" DECLARE best INT;\n" +" DECLARE CONTINUE HANDLER FOR NOT FOUND\n" +" SET bestbody = 0 ;\n" +" \n" +" # Get body with lowest id (RTF before HTML)\n" +" SELECT tag INTO bestbody FROM properties WHERE hierarchyid=hid AND tag IN (0x1009, 0x1013) ORDER BY tag LIMIT 1;\n" +"END;\n"; + +static const char szStreamObj[] = +"# Read a type-5 (Message) item from the database, output properties and subobjects\n" +"CREATE PROCEDURE StreamObj(IN rootid integer, IN maxdepth integer, IN mode integer)\n" +"BEGIN\n" +"DECLARE no_more_rows BOOLEAN;\n" +"DECLARE subid INT;\n" +"DECLARE subsubid INT;\n" +"DECLARE subtype INT;\n" +"DECLARE cur_hierarchy CURSOR FOR\n" +" SELECT id,hierarchy.type FROM hierarchy WHERE parent=rootid AND type=7; \n" +"DECLARE CONTINUE HANDLER FOR NOT FOUND\n" +" SET no_more_rows = TRUE;\n" + +" call GetProps(rootid, mode);\n" + +" call PrepareGetProps(rootid);\n" + +" SELECT id,hierarchy.type FROM hierarchy WHERE parent=rootid;\n" + +" OPEN cur_hierarchy;\n" + +" the_loop: LOOP\n" +" FETCH cur_hierarchy INTO subid, subtype;\n" + +" IF no_more_rows THEN\n" +" CLOSE cur_hierarchy;\n" +" LEAVE the_loop;\n" +" END IF;\n" + +" IF subtype = 7 THEN\n" +" BEGIN\n" +" DECLARE CONTINUE HANDLER FOR NOT FOUND set subsubid = 0;\n" + +" IF maxdepth > 0 THEN\n" +" SELECT id INTO subsubid FROM hierarchy WHERE parent=subid LIMIT 1;\n" +" SELECT id, hierarchy.type FROM hierarchy WHERE parent = subid LIMIT 1;\n" + +" IF subsubid != 0 THEN\n" +" # Recurse into submessage (must be type 5 since attachments can only contain nested messages)\n" +" call StreamObj(subsubid, maxdepth-1, mode);\n" +" END IF;\n" +" ELSE\n" +" # Maximum depth reached. Output a zero-length subset to indicate that we're\n" +" # not recursing further.\n" +" SELECT id, hierarchy.type FROM hierarchy WHERE parent=subid LIMIT 0;\n" +" END IF;\n" +" END;\n" + +" END IF;\n" +" END LOOP the_loop;\n" + +"END\n"; + +static const STOREDPROCS stored_procedures[] = { + { "GetProps", szGetProps }, + { "PrepareGetProps", szPrepareGetProps }, + { "GetBestBody", szGetBestBody }, + { "StreamObj", szStreamObj } +}; + +std::string ECDatabaseMySQL::m_strDatabaseDir; + +std::string zcp_versiontuple::stringify(char sep) const +{ + return ::stringify(v_major) + sep + ::stringify(v_minor) + sep + + ::stringify(v_micro) + sep + ::stringify(v_rev) + sep + + ::stringify(v_schema); +} + +int zcp_versiontuple::compare(const zcp_versiontuple &rhs) const +{ + if (v_major < rhs.v_major) + return -1; + if (v_major > rhs.v_major) + return 1; + if (v_minor < rhs.v_minor) + return -1; + if (v_minor > rhs.v_minor) + return 1; + if (v_micro < rhs.v_micro) + return -1; + if (v_micro > rhs.v_micro) + return 1; + if (v_rev < rhs.v_rev) + return -1; + if (v_rev > rhs.v_rev) + return 1; + return 0; +} + +ECDatabaseMySQL::ECDatabaseMySQL(ECConfig *lpConfig) +{ + m_bMysqlInitialize = false; + m_bConnected = false; + m_bAutoLock = true; + m_lpConfig = lpConfig; + m_bSuppressLockErrorLogging = false; + + // Create a mutex handle for mysql + pthread_mutexattr_t mattr; + pthread_mutexattr_init(&mattr); + pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m_hMutexMySql, &mattr); + +#ifdef DEBUG + m_ulTransactionState = 0; +#endif + +} + +ECDatabaseMySQL::~ECDatabaseMySQL() +{ + Close(); + // Close the mutex handle of mysql + pthread_mutex_destroy(&m_hMutexMySql); +} + +ECRESULT ECDatabaseMySQL::InitLibrary(const char *lpDatabaseDir, + const char *lpConfigFile) +{ + ECRESULT er = erSuccess; + string strDatabaseDir; + string strConfigFile; + int ret = 0; + + if(lpDatabaseDir) { + strDatabaseDir = "--datadir="; + strDatabaseDir+= lpDatabaseDir; + + m_strDatabaseDir = lpDatabaseDir; + } + + if(lpConfigFile) { + strConfigFile = "--defaults-file="; + strConfigFile+= lpConfigFile; + } + + const char *server_args[] = { + "zarafa", /* this string is not used */ + strConfigFile.c_str(), + strDatabaseDir.c_str(), + }; + /* + * mysql's function signature stinks, and even their samples + * do the cast :( + */ + if ((ret = mysql_library_init(arraySize(server_args), + const_cast<char **>(server_args), + const_cast<char **>(server_groups))) != 0) { + ec_log_crit("Unable to initialize mysql: error 0x%08X", ret); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + +exit: + return er; +} + +/** + * Initialize anything that has to be initialized at server startup. + * + * Currently this means we're updating all the stored procedure definitions + */ +ECRESULT ECDatabaseMySQL::InitializeDBState(void) +{ + ECRESULT ret = InitializeDBStateInner(); + +#ifdef HAVE_OFFLINE_SUPPORT + /* Only do this recovery on the Windows thing */ + if (ret == erSuccess) + return ret; + + /* Start "repair" */ + ec_log_err("InitializeDBState unsuccessful: %s (%d). Attempting drop, and retry.", + GetMAPIErrorMessage(ZarafaErrorToMAPIError(ret, hrSuccess)), + ret); + ret = DoUpdate("DROP TABLE mysql.proc"); + if (ret != erSuccess) + goto exit; + ret = DoUpdate("DROP TABLE mysql.plugin"); + if (ret != erSuccess) + goto exit; + ret = DoUpdate("DROP TABLE mysql.innodb_table_stats"); + if (ret != erSuccess) + goto exit; + ret = DoUpdate("DROP TABLE mysql.innodb_index_stats"); + if (ret != erSuccess) + goto exit; + return InitializeDBStateInner(); + exit: + ec_log_err("Error during drop"); +#endif + + return ret; +} + +ECRESULT ECDatabaseMySQL::InitializeDBStateInner() +{ + ECRESULT er = erSuccess; + +#ifdef HAVE_OFFLINE_SUPPORT + // Unsure the stored procedures table is available + + const char szProc[] = + "CREATE TABLE IF NOT EXISTS mysql.proc (db char(64) collate utf8_bin DEFAULT '' NOT NULL, name char(64) DEFAULT '' NOT NULL, type enum('FUNCTION','PROCEDURE') NOT NULL, specific_name char(64) DEFAULT '' NOT NULL, language enum('SQL') DEFAULT 'SQL' NOT NULL, sql_data_access enum( 'CONTAINS_SQL', 'NO_SQL', 'READS_SQL_DATA', 'MODIFIES_SQL_DATA') DEFAULT 'CONTAINS_SQL' NOT NULL, is_deterministic enum('YES','NO') DEFAULT 'NO' NOT NULL, security_type enum('INVOKER','DEFINER') DEFAULT 'DEFINER' NOT NULL, param_list blob NOT NULL, returns longblob NOT NULL, body longblob NOT NULL, definer char(141) collate utf8_bin DEFAULT '' NOT NULL, created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, modified timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', sql_mode set( 'REAL_AS_FLOAT', 'PIPES_AS_CONCAT', 'ANSI_QUOTES', 'IGNORE_SPACE', 'IGNORE_BAD_TABLE_OPTIONS', 'ONLY_FULL_GROUP_BY', 'NO_UNSIGNED_SUBTRACTION', 'NO_DIR_IN_CREATE', 'POSTGRESQL', 'ORACLE', 'MSSQL', 'DB2', 'MAXDB', 'NO_KEY_OPTIONS', 'NO_TABLE_OPTIONS', 'NO_FIELD_OPTIONS', 'MYSQL323', 'MYSQL40', 'ANSI', 'NO_AUTO_VALUE_ON_ZERO', 'NO_BACKSLASH_ESCAPES', 'STRICT_TRANS_TABLES', 'STRICT_ALL_TABLES', 'NO_ZERO_IN_DATE', 'NO_ZERO_DATE', 'INVALID_DATES', 'ERROR_FOR_DIVISION_BY_ZERO', 'TRADITIONAL', 'NO_AUTO_CREATE_USER', 'HIGH_NOT_PRECEDENCE', 'NO_ENGINE_SUBSTITUTION', 'PAD_CHAR_TO_FULL_LENGTH') DEFAULT '' NOT NULL, comment text collate utf8_bin NOT NULL, character_set_client char(32) collate utf8_bin, collation_connection char(32) collate utf8_bin, db_collation char(32) collate utf8_bin, body_utf8 longblob, PRIMARY KEY (db,name,type)) engine=MyISAM character set utf8 comment='Stored Procedures';"; + const char szPlugin[] = + "CREATE TABLE IF NOT EXISTS mysql.plugin (name varchar(64) DEFAULT '' NOT NULL, dl varchar(128) DEFAULT '' NOT NULL, PRIMARY KEY (name)) engine=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci comment='MySQL plugins';"; + const char szInnoStats[] = + "CREATE TABLE IF NOT EXISTS mysql.innodb_table_stats ( database_name VARCHAR(64) NOT NULL, table_name VARCHAR(64) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, n_rows BIGINT UNSIGNED NOT NULL, clustered_index_size BIGINT UNSIGNED NOT NULL, sum_of_other_index_sizes BIGINT UNSIGNED NOT NULL, PRIMARY KEY (database_name, table_name)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0;"; + const char szInnoIndexStats[] = + "CREATE TABLE IF NOT EXISTS mysql.innodb_index_stats ( database_name VARCHAR(64) NOT NULL, table_name VARCHAR(64) NOT NULL, index_name VARCHAR(64) NOT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, stat_name VARCHAR(64) NOT NULL, stat_value BIGINT UNSIGNED NOT NULL, sample_size BIGINT UNSIGNED, stat_description VARCHAR(1024) NOT NULL, PRIMARY KEY (database_name, table_name, index_name, stat_name)) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0;"; + + er = DoUpdate("CREATE DATABASE IF NOT EXISTS mysql"); + if(er != erSuccess) + goto exit; + + er = DoUpdate(szProc); + if(er != erSuccess) + goto exit; + + er = DoUpdate(szPlugin); + if(er != erSuccess) + goto exit; + + er = DoUpdate(szInnoStats); + if(er != erSuccess) + goto exit; + + er = DoUpdate(szInnoIndexStats); + if(er != erSuccess) + goto exit; +#endif + + for (unsigned int i = 0; i < arraySize(stored_procedures); ++i) { + er = DoUpdate(std::string("DROP PROCEDURE IF EXISTS ") + stored_procedures[i].szName); + if(er != erSuccess) + goto exit; + + er = DoUpdate(stored_procedures[i].szSQL); + if(er != erSuccess) { + int err = mysql_errno(&m_lpMySQL); + if (err == ER_DBACCESS_DENIED_ERROR) { + ec_log_err("zarafa-server is not allowed to create stored procedures"); + ec_log_err("Please grant CREATE ROUTINE permissions to the mysql user \"%s\" on the \"%s\" database", + m_lpConfig->GetSetting("mysql_user"), m_lpConfig->GetSetting("mysql_database")); + } else { + ec_log_err("zarafa-server is unable to create stored procedures, error %d", err); + } + goto exit; + } + } + +exit: + return er; +} + +void ECDatabaseMySQL::UnloadLibrary(void) +{ + /* + * MySQL will timeout waiting for its own threads if the mysql + * initialization was done in another thread than the one where + * mysql_*_end() is called. [Global problem - it also affects + * projects other than Zarafa's.] :( + */ + ec_log_notice("Waiting for mysql_server_end"); + mysql_server_end();// mysql > 4.1.10 = mysql_library_end(); + ec_log_notice("Waiting for mysql_library_end"); + mysql_library_end(); +} + +ECRESULT ECDatabaseMySQL::InitEngine() +{ + ECRESULT er = erSuccess; + + _ASSERT(m_bMysqlInitialize == false); + + //Init mysql and make a connection + if(mysql_init(&m_lpMySQL) == NULL) { + ec_log_crit("ECDatabaseMySQL::InitEngine(): mysql_init failed"); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + + m_bMysqlInitialize = true; + + // Set auto reconnect OFF + // mysql < 5.0.4 default on, mysql 5.0.4 > reconnection default off + // Zarafa always wants reconnect OFF, because we want to know when the connection + // is broken since this creates a new MySQL session, and we want to set some session + // variables + m_lpMySQL.reconnect = 0; + +exit: + return er; +} + +std::string ECDatabaseMySQL::GetDatabaseDir() +{ + ASSERT(!m_strDatabaseDir.empty()); + return m_strDatabaseDir; +} + +ECRESULT ECDatabaseMySQL::CheckExistColumn(const std::string &strTable, const std::string &strColumn, bool *lpbExist) +{ + ECRESULT er = erSuccess; + std::string strQuery; + DB_RESULT lpDBResult = NULL; + + strQuery = "SELECT 1 FROM information_schema.COLUMNS " + "WHERE TABLE_SCHEMA = '" + string(m_lpConfig->GetSetting("mysql_database")) + "' " + "AND TABLE_NAME = '" + strTable + "' " + "AND COLUMN_NAME = '" + strColumn + "'"; + + er = DoSelect(strQuery, &lpDBResult); + if (er != erSuccess) + goto exit; + + *lpbExist = (FetchRow(lpDBResult) != NULL); + +exit: + if (lpDBResult) + FreeResult(lpDBResult); + + return er; +} + +ECRESULT ECDatabaseMySQL::CheckExistIndex(const std::string &strTable, + const std::string &strKey, bool *lpbExist) +{ + ECRESULT er = erSuccess; + std::string strQuery; + DB_RESULT lpDBResult = NULL; + DB_ROW lpRow = NULL; + + // WHERE not supported in MySQL < 5.0.3 + strQuery = "SHOW INDEXES FROM " + strTable; + + er = DoSelect(strQuery, &lpDBResult); + if (er != erSuccess) + goto exit; + + *lpbExist = false; + while ((lpRow = FetchRow(lpDBResult)) != NULL) { + // 2 is Key_name + if (lpRow[2] && strcmp(lpRow[2], strKey.c_str()) == 0) { + *lpbExist = true; + break; + } + } + +exit: + if (lpDBResult) + FreeResult(lpDBResult); + + return er; +} + +ECRESULT ECDatabaseMySQL::Connect() +{ + ECRESULT er = erSuccess; + std::string strQuery; + const char *lpMysqlPort = m_lpConfig->GetSetting("mysql_port"); + const char *lpMysqlSocket = m_lpConfig->GetSetting("mysql_socket"); + DB_RESULT lpDBResult = NULL; + DB_ROW lpDBRow = NULL; + unsigned int gcm = 0; + + if (*lpMysqlSocket == '\0') + lpMysqlSocket = NULL; + + er = InitEngine(); + if(er != erSuccess) + goto exit; + + if(mysql_real_connect(&m_lpMySQL, + m_lpConfig->GetSetting("mysql_host"), + m_lpConfig->GetSetting("mysql_user"), + m_lpConfig->GetSetting("mysql_password"), + m_lpConfig->GetSetting("mysql_database"), + (lpMysqlPort)?atoi(lpMysqlPort):0, + lpMysqlSocket, CLIENT_MULTI_STATEMENTS) == NULL) + { + if(mysql_errno(&m_lpMySQL) == ER_BAD_DB_ERROR) // Database does not exist + er = ZARAFA_E_DATABASE_NOT_FOUND; + else + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("ECDatabaseMySQL::Connect(): mysql connect fail %x", er); + goto exit; + } + + // Check if the database is available, but empty + strQuery = "SHOW tables"; + er = DoSelect(strQuery, &lpDBResult); + if(er != erSuccess) + goto exit; + + if(GetNumRows(lpDBResult) == 0) { + er = ZARAFA_E_DATABASE_NOT_FOUND; + goto exit; + } + + if(lpDBResult) { + FreeResult(lpDBResult); + lpDBResult = NULL; + } + + strQuery = "SHOW variables LIKE 'max_allowed_packet'"; + er = DoSelect(strQuery, &lpDBResult); + if(er != erSuccess) + goto exit; + + lpDBRow = FetchRow(lpDBResult); + /* lpDBRow[0] has the variable name, [1] the value */ + if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL) { + ec_log_warn("Unable to retrieve max_allowed_packet value. Assuming 16M"); + m_ulMaxAllowedPacket = (unsigned int)MAX_ALLOWED_PACKET; + } else { + m_ulMaxAllowedPacket = atoui(lpDBRow[1]); + } + + m_bConnected = true; + +#if HAVE_MYSQL_SET_CHARACTER_SET + // function since mysql 5.0.7 + if (mysql_set_character_set(&m_lpMySQL, "utf8")) { + ec_log_err("Unable to set character set to \"utf8\""); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } +#else + if (Query("set character_set_client = 'utf8'") != 0) { + ec_log_warn("Unable to set character_set_client value"); + goto exit; + } + if (Query("set character_set_connection = 'utf8'") != 0) { + ec_log_warn("Unable to set character_set_connection value"); + goto exit; + } + if (Query("set character_set_results = 'utf8'") != 0) { + ec_log_warn("Unable to set character_set_results value"); + goto exit; + } +#endif + if (Query("set max_sp_recursion_depth = 255") != 0) { + ec_log_err("Unable to set recursion depth"); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + + // Force to a sane value + gcm = atoui(m_lpConfig->GetSetting("mysql_group_concat_max_len")); + if(gcm < 1024) + gcm = 1024; + + strQuery = (string)"SET SESSION group_concat_max_len = " + stringify(gcm); + if(Query(strQuery) != erSuccess ) { + ec_log_warn("Unable to set group_concat_max_len value"); + } + + // changing the SESSION max_allowed_packet is removed since mysql 5.1, and GLOBAL is for SUPER users only, so just give a warning + if (m_ulMaxAllowedPacket < MAX_ALLOWED_PACKET) + ec_log_warn("max_allowed_packet is smaller than 16M (%d). You are advised to increase this value by adding max_allowed_packet=16M in the [mysqld] section of my.cnf.", m_ulMaxAllowedPacket); + + if (m_lpMySQL.server_version) { + // m_lpMySQL.server_version is a C type string (char*) containing something like "5.5.37-0+wheezy1" (MySQL), + // "5.5.37-MariaDB-1~wheezy-log" or "10.0.11-MariaDB=1~wheezy-log" (MariaDB) + // The following code may look funny, but it is correct, see http://www.cplusplus.com/reference/cstdlib/strtol/ + long int majorversion = strtol(m_lpMySQL.server_version, NULL, 10); + // Check for over/underflow and version. + if (errno != ERANGE && majorversion >= 5) { + // this option was introduced in mysql 5.0, so let's not even try on 4.1 servers + strQuery = "SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_UNSIGNED_SUBTRACTION'"; + Query(strQuery); // ignore error + } + } + +exit: + if(lpDBResult) + FreeResult(lpDBResult); + + if (er == erSuccess) + g_lpStatsCollector->Increment(SCN_DATABASE_CONNECTS); + else + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_CONNECTS); + + return er; +} + +ECRESULT ECDatabaseMySQL::Close() +{ + ECRESULT er = erSuccess; + + //INFO: No locking here + + m_bConnected = false; + + // Close mysql data connection and deallocate data + if(m_bMysqlInitialize) + mysql_close(&m_lpMySQL); + + m_bMysqlInitialize = false; + + return er; +} + +// Get database ownership +void ECDatabaseMySQL::Lock() +{ + pthread_mutex_lock(&m_hMutexMySql); +} + +// Release the database ownership +void ECDatabaseMySQL::UnLock() +{ + pthread_mutex_unlock(&m_hMutexMySql); +} + +bool ECDatabaseMySQL::isConnected() { + + return m_bConnected; +} + +/** + * Perform an SQL query on MySQL + * + * Sends a query to the MySQL server, and does a reconnect if the server connection is lost before or during + * the SQL query. The reconnect is done only once. If the query fails after the reconnect, the entire call + * fails. + * + * It is up to the caller to get any result information from the query. + * + * @param[in] strQuery SQL query to perform + * @return result (erSuccess or ZARAFA_E_DATABASE_ERROR) + */ +ECRESULT ECDatabaseMySQL::Query(const string &strQuery) { + ECRESULT er = erSuccess; + int err; + + LOG_SQL_DEBUG("SQL [%08lu]: \"%s;\"", m_lpMySQL.thread_id, strQuery.c_str()); + + // use mysql_real_query to be binary safe ( http://dev.mysql.com/doc/mysql/en/mysql-real-query.html ) + err = mysql_real_query( &m_lpMySQL, strQuery.c_str(), strQuery.length() ); + + if(err && (mysql_errno(&m_lpMySQL) == CR_SERVER_LOST || mysql_errno(&m_lpMySQL) == CR_SERVER_GONE_ERROR)) { + ec_log_warn("SQL [%08lu] info: Try to reconnect", m_lpMySQL.thread_id); + + er = Close(); + if(er != erSuccess) + goto exit; + + er = Connect(); + if(er != erSuccess) + goto exit; + + // Try again + err = mysql_real_query( &m_lpMySQL, strQuery.c_str(), strQuery.length() ); + } + + if(err) { + if (!m_bSuppressLockErrorLogging || GetLastError() == DB_E_UNKNOWN) + 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()); + er = ZARAFA_E_DATABASE_ERROR; + // Don't assert on ER_NO_SUCH_TABLE because it's an anticipated error in the db upgrade code. + if (mysql_errno(&m_lpMySQL) != ER_NO_SUCH_TABLE) + ASSERT(false); + } + +exit: + return er; +} + +/** + * Perform a SELECT operation on the database + * + * Sends the passed SELECT-like (any operation that outputs a result set) query to the MySQL server and retrieves + * the result. + * + * Setting fStreamResult will delay retrieving data from the network until FetchRow() is called. The only + * drawback is that GetRowCount() can therefore not be used unless all rows are fetched first. The main reason to + * use this is to conserve memory and increase pipelining (the client can start processing data before the server + * has completed the query) + * + * @param[in] strQuery SELECT query string + * @param[out] Result output + * @param[in] fStreamResult TRUE if data should be streamed instead of stored + * @return result erSuccess or ZARAFA_E_DATABASE_ERROR + */ +ECRESULT ECDatabaseMySQL::DoSelect(const string &strQuery, DB_RESULT *lppResult, bool fStreamResult) { + + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + + _ASSERT(strQuery.length()!= 0); + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + + if( Query(strQuery) != erSuccess ) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("ECDatabaseMySQL::DoSelect(): query failed"); + goto exit; + } + + if (fStreamResult) + lpResult = mysql_use_result(&m_lpMySQL); + else + lpResult = mysql_store_result(&m_lpMySQL); + + if( lpResult == NULL ) { + er = ZARAFA_E_DATABASE_ERROR; + if (!m_bSuppressLockErrorLogging || GetLastError() == DB_E_UNKNOWN) + ec_log_err("SQL [%08lu] result failed: %s, Query: \"%s\"", m_lpMySQL.thread_id, mysql_error(&m_lpMySQL), strQuery.c_str()); + } + + g_lpStatsCollector->Increment(SCN_DATABASE_SELECTS); + + if (lppResult) + *lppResult = lpResult; + else if(lpResult) + FreeResult(lpResult); + +exit: + if (er != erSuccess) { + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_SELECTS); + g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL)); + } + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +ECRESULT ECDatabaseMySQL::DoSelectMulti(const string &strQuery) { + + ECRESULT er = erSuccess; + + _ASSERT(strQuery.length()!= 0); + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + + if( Query(strQuery) != erSuccess ) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("ECDatabaseMySQL::DoSelectMulti(): select failed"); + goto exit; + } + + m_bFirstResult = true; + + g_lpStatsCollector->Increment(SCN_DATABASE_SELECTS); + +exit: + if (er != erSuccess) { + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_SELECTS); + g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL)); + } + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +/** + * Get next resultset from a multi-resultset query + * + * @param[out] lppResult Resultset + * @return result + */ +ECRESULT ECDatabaseMySQL::GetNextResult(DB_RESULT *lppResult) { + + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + int ret = 0; + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + + if(!m_bFirstResult) + ret = mysql_next_result( &m_lpMySQL ); + + m_bFirstResult = false; + + if(ret < 0) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("SQL [%08lu] next_result failed: expected more results", m_lpMySQL.thread_id); + goto exit; + } + + if(ret > 0) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("SQL [%08lu] next_result of multi-resultset failed: %s", m_lpMySQL.thread_id, mysql_error(&m_lpMySQL)); + goto exit; + } + + lpResult = mysql_store_result( &m_lpMySQL ); + if(lpResult == NULL) { + // I think this can only happen on the first result set of a query since otherwise mysql_next_result() would already fail + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("SQL [%08lu] result failed: %s", m_lpMySQL.thread_id, mysql_error(&m_lpMySQL)); + goto exit; + } + + if (lppResult) + *lppResult = lpResult; + else { + if(lpResult) + FreeResult(lpResult); + } + +exit: + if (er != erSuccess) { + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_SELECTS); + g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL)); + } + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +/** + * Finalize a multi-SQL call + * + * A stored procedure will output a NULL result set at the end of the stored procedure to indicate + * that the results have ended. This must be consumed here, otherwise the database will be left in a bad + * state. + */ +ECRESULT ECDatabaseMySQL::FinalizeMulti() { + + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + + mysql_next_result(&m_lpMySQL); + + lpResult = mysql_store_result(&m_lpMySQL); + + if(lpResult != NULL) { + ec_log_err("SQL [%08lu] result failed: unexpected results received at end of batch", m_lpMySQL.thread_id); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } +exit: + if(lpResult) + FreeResult(lpResult); + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +/** + * Perform a UPDATE operation on the database + * + * Sends the passed UPDATE query to the MySQL server, and optionally returns the number of affected rows. The + * affected rows is the number of rows that have been MODIFIED, which is not necessarily the number of rows that + * MATCHED the WHERE-clause. + * + * @param[in] strQuery UPDATE query string + * @param[out] lpulAffectedRows (optional) Receives the number of affected rows + * @return result erSuccess or ZARAFA_E_DATABASE_ERROR + */ +ECRESULT ECDatabaseMySQL::DoUpdate(const string &strQuery, unsigned int *lpulAffectedRows) { + + ECRESULT er = erSuccess; + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + + er = _Update(strQuery, lpulAffectedRows); + + if (er != erSuccess) { + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_UPDATES); + g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL)); + } + + g_lpStatsCollector->Increment(SCN_DATABASE_UPDATES); + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +ECRESULT ECDatabaseMySQL::_Update(const string &strQuery, unsigned int *lpulAffectedRows) +{ + ECRESULT er = erSuccess; + + if( Query(strQuery) != erSuccess ) { + // FIXME: Add the mysql error system ? + // er = nMysqlError; + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("ECDatabaseMySQL::_Update() query failed"); + goto exit; + } + + if(lpulAffectedRows) + *lpulAffectedRows = GetAffectedRows(); + +exit: + return er; +} + +/** + * Perform an INSERT operation on the database + * + * Sends the passed INSERT query to the MySQL server, and optionally returns the new insert ID and the number of inserted + * rows. + * + * @param[in] strQuery INSERT query string + * @param[out] lpulInsertId (optional) Receives the last insert id + * @param[out] lpulAffectedRows (optional) Receives the number of inserted rows + * @return result erSuccess or ZARAFA_E_DATABASE_ERROR + */ +ECRESULT ECDatabaseMySQL::DoInsert(const string &strQuery, unsigned int *lpulInsertId, unsigned int *lpulAffectedRows) +{ + ECRESULT er = erSuccess; + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + + er = _Update(strQuery, lpulAffectedRows); + + if (er == erSuccess) { + if (lpulInsertId) + *lpulInsertId = GetInsertId(); + } else { + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_INSERTS); + g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL)); + } + + g_lpStatsCollector->Increment(SCN_DATABASE_INSERTS); + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +/** + * Perform a DELETE operation on the database + * + * Sends the passed DELETE query to the MySQL server, and optionally the number of deleted rows + * + * @param[in] strQuery INSERT query string + * @param[out] lpulAffectedRows (optional) Receives the number of deleted rows + * @return result erSuccess or ZARAFA_E_DATABASE_ERROR + */ +ECRESULT ECDatabaseMySQL::DoDelete(const string &strQuery, unsigned int *lpulAffectedRows) { + + ECRESULT er = erSuccess; + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + + er = _Update(strQuery, lpulAffectedRows); + + if (er != erSuccess) { + g_lpStatsCollector->Increment(SCN_DATABASE_FAILED_DELETES); + g_lpStatsCollector->SetTime(SCN_DATABASE_LAST_FAILED, time(NULL)); + } + + g_lpStatsCollector->Increment(SCN_DATABASE_DELETES); + + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +/* + * This function updates a sequence in an atomic fashion - if called correctly; + * + * To make it work correctly, the state of the database connection should *NOT* be in a transaction; this would delay + * committing of the data until a later time, causing other concurrent threads to possibly generate the same ID or lock + * while waiting for this transaction to end. So, don't call Begin() before calling this function unless you really + * know what you're doing. + * + * @todo measure sequence update calls, currently it is a update + */ +ECRESULT ECDatabaseMySQL::DoSequence(const std::string &strSeqName, unsigned int ulCount, unsigned long long *lpllFirstId) { + ECRESULT er = erSuccess; + unsigned int ulAffected = 0; + + // Autolock, lock data + if(m_bAutoLock) + Lock(); + +#ifdef DEBUG +#if DEBUG_TRANSACTION + if(m_ulTransactionState != 0) { + ASSERT(FALSE); + } +#endif +#endif + + // Attempt to update the sequence in an atomic fashion + er = DoUpdate("UPDATE settings SET value=LAST_INSERT_ID(value+1)+" + stringify(ulCount-1) + " WHERE name = '" + strSeqName + "'", &ulAffected); + if(er != erSuccess) + goto exit; + + // If the setting was missing, insert it now, starting at sequence 1 (not 0 for safety - maybe there's some if(ulSequenceId) code somewhere) + if(ulAffected == 0) { + er = Query("INSERT INTO settings (name, value) VALUES('" + strSeqName + "',LAST_INSERT_ID(1)+" + stringify(ulCount-1) + ")"); + if(er != erSuccess) + goto exit; + } + + *lpllFirstId = mysql_insert_id(&m_lpMySQL); + +exit: + // Autolock, unlock data + if(m_bAutoLock) + UnLock(); + + return er; +} + +unsigned int ECDatabaseMySQL::GetAffectedRows() { + + return (unsigned int)mysql_affected_rows(&m_lpMySQL); +} + +unsigned int ECDatabaseMySQL::GetInsertId() { + + return (unsigned int)mysql_insert_id(&m_lpMySQL); +} + +void ECDatabaseMySQL::FreeResult(DB_RESULT sResult) { + + _ASSERT(sResult != NULL); + + if(sResult) + mysql_free_result((MYSQL_RES *)sResult); +} + +unsigned int ECDatabaseMySQL::GetNumRows(DB_RESULT sResult) { + + return (unsigned int)mysql_num_rows((MYSQL_RES *)sResult); +} + +unsigned int ECDatabaseMySQL::GetNumRowFields(DB_RESULT sResult) { + + return mysql_num_fields((MYSQL_RES *)sResult); +} + +unsigned int ECDatabaseMySQL::GetRowIndex(DB_RESULT sResult, const std::string &strFieldname) { + + MYSQL_FIELD *lpFields; + unsigned int cbFields; + unsigned int ulIndex = (unsigned int)-1; + + lpFields = mysql_fetch_fields((MYSQL_RES *)sResult); + cbFields = mysql_field_count(&m_lpMySQL); + + for (unsigned int i = 0; i < cbFields && ulIndex == (unsigned int)-1; ++i) + if (stricmp(lpFields[i].name, strFieldname.c_str()) == 0) + ulIndex = i; + + return ulIndex; +} + +DB_ROW ECDatabaseMySQL::FetchRow(DB_RESULT sResult) { + + return mysql_fetch_row((MYSQL_RES *)sResult); +} + +DB_LENGTHS ECDatabaseMySQL::FetchRowLengths(DB_RESULT sResult) { + + return (DB_LENGTHS)mysql_fetch_lengths((MYSQL_RES *)sResult); +} + +/** + * For some reason, MySQL only supports up to 3 bytes of utf-8 data. This means + * that data outside the BMP is not supported. This function filters the passed utf-8 string + * and removes the non-BMP characters. Since it should be extremely uncommon to have useful + * data outside the BMP, this should be acceptable. + * + * Note: BMP stands for Basic Multilingual Plane (first 0x10000 code points in unicode) + * + * If somebody points out a useful use case for non-BMP characters in the future, then we'll + * have to rethink this. + * + */ +std::string ECDatabaseMySQL::FilterBMP(const std::string &strToFilter) +{ + const char *c = strToFilter.c_str(); + std::string::size_type pos = 0; + std::string strFiltered; + + while(pos < strToFilter.size()) { + // Copy 1, 2, and 3-byte utf-8 sequences + int len; + + if((c[pos] & 0x80) == 0) + len = 1; + else if((c[pos] & 0xE0) == 0xC0) + len = 2; + else if((c[pos] & 0xF0) == 0xE0) + len = 3; + else if((c[pos] & 0xF8) == 0xF0) + len = 4; + else if((c[pos] & 0xFC) == 0xF8) + len = 5; + else if((c[pos] & 0xFE) == 0xFC) + len = 6; + else { + // Invalid utf-8 ? + len = 1; + } + + if(len < 4) { + strFiltered.append(&c[pos], len); + } + + pos += len; + } + + return strFiltered; +} + +std::string ECDatabaseMySQL::Escape(const std::string &strToEscape) +{ + ULONG size = strToEscape.length()*2+1; + char *szEscaped = new char[size]; + std::string escaped; + + memset(szEscaped, 0, size); + + mysql_real_escape_string(&this->m_lpMySQL, szEscaped, strToEscape.c_str(), strToEscape.length()); + + escaped = szEscaped; + + delete [] szEscaped; + + return escaped; +} + +std::string ECDatabaseMySQL::EscapeBinary(unsigned char *lpData, unsigned int ulLen) +{ + ULONG size = ulLen*2+1; + char *szEscaped = new char[size]; + std::string escaped; + + memset(szEscaped, 0, size); + + mysql_real_escape_string(&this->m_lpMySQL, szEscaped, (const char *)lpData, ulLen); + + escaped = szEscaped; + + delete [] szEscaped; + + return "'" + escaped + "'"; +} + +std::string ECDatabaseMySQL::EscapeBinary(const std::string& strData) +{ + return EscapeBinary((unsigned char *)strData.c_str(), strData.size()); +} + +void ECDatabaseMySQL::ResetResult(DB_RESULT sResult) { + + mysql_data_seek((MYSQL_RES *)sResult, 0); +} + +const char *ECDatabaseMySQL::GetError(void) +{ + if(m_bMysqlInitialize == false) + return "MYSQL not initialized"; + + return mysql_error(&m_lpMySQL); +} + +DB_ERROR ECDatabaseMySQL::GetLastError() +{ + DB_ERROR dberr; + + switch (mysql_errno(&m_lpMySQL)) { + case ER_LOCK_WAIT_TIMEOUT: + dberr = DB_E_LOCK_WAIT_TIMEOUT; + break; + + case ER_LOCK_DEADLOCK: + dberr = DB_E_LOCK_DEADLOCK; + break; + + default: + dberr = DB_E_UNKNOWN; + break; + } + + return dberr; +} + +bool ECDatabaseMySQL::SuppressLockErrorLogging(bool bSuppress) +{ + std::swap(bSuppress, m_bSuppressLockErrorLogging); + return bSuppress; +} + +ECRESULT ECDatabaseMySQL::Begin() { + ECRESULT er = erSuccess; + + er = Query("BEGIN"); + +#ifdef DEBUG +#if DEBUG_TRANSACTION + ec_log_debug("%08X: BEGIN", &m_lpMySQL); + if(m_ulTransactionState != 0) { + ec_log_debug("BEGIN ALREADY ISSUED"); + ASSERT(("BEGIN ALREADY ISSUED", FALSE)); + } + m_ulTransactionState = 1; +#endif +#endif + + return er; +} + +ECRESULT ECDatabaseMySQL::Commit() { + ECRESULT er = erSuccess; + er = Query("COMMIT"); + +#ifdef DEBUG +#if DEBUG_TRANSACTION + ec_log_debug("%08X: COMMIT", &m_lpMySQL); + if(m_ulTransactionState != 1) { + ec_log_debug("NO BEGIN ISSUED"); + ASSERT(("NO BEGIN ISSUED", FALSE)); + } + m_ulTransactionState = 0; +#endif +#endif + + return er; +} + +ECRESULT ECDatabaseMySQL::Rollback() { + ECRESULT er = erSuccess; +#ifdef HAVE_OFFLINE_SUPPORT + const char *lpQuery = "SHOW ENGINE INNODB STATUS"; + int err = 0; +#endif + + er = Query("ROLLBACK"); + +#ifdef DEBUG +#if DEBUG_TRANSACTION + ec_log_debug("%08X: ROLLBACK", &m_lpMySQL); + if(m_ulTransactionState != 1) { + ec_log_debug("NO BEGIN ISSUED"); + ASSERT(("NO BEGIN ISSUED", FALSE)); + } + m_ulTransactionState = 0; +#endif +#endif +#ifdef HAVE_OFFLINE_SUPPORT + // We'll log the innodb status after each rollback. This shouldn't + // happen too much though. + err = mysql_real_query(&m_lpMySQL, lpQuery, strlen(lpQuery)); + if (!err) { + MYSQL_RES *lpResult = mysql_use_result(&m_lpMySQL); + if (lpResult) { + MYSQL_ROW row = mysql_fetch_row(lpResult); + if (row) { + unsigned int fields = mysql_num_fields(lpResult); + for (unsigned int i = 0; i < fields; ++i) + if (row[i]) + ec_log_err("%s", row[i]); + } + mysql_free_result(lpResult); + } + } +#endif + + return er; +} + +unsigned int ECDatabaseMySQL::GetMaxAllowedPacket() { + return m_ulMaxAllowedPacket; +} + +void ECDatabaseMySQL::ThreadInit() { + mysql_thread_init(); +} + +void ECDatabaseMySQL::ThreadEnd() { + mysql_thread_end(); +} + +ECRESULT ECDatabaseMySQL::IsInnoDBSupported() +{ + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + + er = DoSelect("SHOW ENGINES", &lpResult); + if(er != erSuccess) { + ec_log_crit("Unable to query supported database engines. Error: %s", GetError()); + goto exit; + } + + while ((lpDBRow = FetchRow(lpResult)) != NULL) { + if (stricmp(lpDBRow[0], "InnoDB") != 0) + continue; + + if (stricmp(lpDBRow[1], "DISABLED") == 0) { + // mysql has run with innodb enabled once, but disabled this.. so check your log. + ec_log_crit("INNODB engine is disabled. Please re-enable the INNODB engine. Check your MySQL log for more information or comment out skip-innodb in the mysql configuration file."); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } else if (stricmp(lpDBRow[1], "YES") != 0 && stricmp(lpDBRow[1], "DEFAULT") != 0) { + // mysql is incorrectly configured or compiled. + ec_log_crit("INNODB engine is not supported. Please enable the INNODB engine in the mysql configuration file."); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + break; + } + if (lpDBRow == NULL) { + ec_log_crit("Unable to find the \"InnoDB\" engine from the mysql server. Probably INNODB is not supported."); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + +exit: + if(lpResult) + FreeResult(lpResult); + + return er; +} + +ECRESULT ECDatabaseMySQL::CreateDatabase() +{ + ECRESULT er = erSuccess; + string strQuery; + const char *lpDatabase = m_lpConfig->GetSetting("mysql_database"); + const char *lpMysqlPort = m_lpConfig->GetSetting("mysql_port"); + const char *lpMysqlSocket = m_lpConfig->GetSetting("mysql_socket"); + + if(*lpMysqlSocket == '\0') + lpMysqlSocket = NULL; + + // Zarafa database tables + static const sSQLDatabase_t sDatabaseTables[] = { + {"acl", Z_TABLEDEF_ACL}, + {"hierarchy", Z_TABLEDEF_HIERARCHY}, + {"names", Z_TABLEDEF_NAMES}, + {"mvproperties", Z_TABLEDEF_MVPROPERTIES}, + {"tproperties", Z_TABLEDEF_TPROPERTIES}, + {"properties", Z_TABLEDEF_PROPERTIES}, + {"delayedupdate", Z_TABLEDEF_DELAYEDUPDATE}, + {"receivefolder", Z_TABLEDEF_RECEIVEFOLDER}, + + {"stores", Z_TABLEDEF_STORES}, + {"users", Z_TABLEDEF_USERS}, + {"outgoingqueue", Z_TABLEDEF_OUTGOINGQUEUE}, + {"lob", Z_TABLEDEF_LOB}, + {"searchresults", Z_TABLEDEF_SEARCHRESULTS}, + {"changes", Z_TABLEDEF_CHANGES}, + {"syncs", Z_TABLEDEF_SYNCS}, + {"versions", Z_TABLEDEF_VERSIONS}, + {"indexedproperties", Z_TABLEDEF_INDEXED_PROPERTIES}, + {"settings", Z_TABLEDEF_SETTINGS}, + + {"object", Z_TABLEDEF_OBJECT}, + {"objectproperty", Z_TABLEDEF_OBJECT_PROPERTY}, + {"objectmvproperty", Z_TABLEDEF_OBJECT_MVPROPERTY}, + {"objectrelation", Z_TABLEDEF_OBJECT_RELATION}, + + {"singleinstances", Z_TABLEDEF_REFERENCES }, + {"abchanges", Z_TABLEDEF_ABCHANGES }, + {"syncedmessages", Z_TABLEDEFS_SYNCEDMESSAGES }, + {"clientupdatestatus", Z_TABLEDEF_CLIENTUPDATESTATUS }, + }; + + // Zarafa database default data + static const sSQLDatabase_t sDatabaseData[] = { + {"users", Z_TABLEDATA_USERS}, + {"stores", Z_TABLEDATA_STORES}, + {"hierarchy", Z_TABLEDATA_HIERARCHY}, + {"properties", Z_TABLEDATA_PROPERTIES}, + {"acl", Z_TABLEDATA_ACL}, + {"settings", Z_TABLEDATA_SETTINGS}, + {"indexedproperties", Z_TABLEDATA_INDEXED_PROPERTIES}, + }; + + er = InitEngine(); + if(er != erSuccess) + goto exit; + + // Connect + if(mysql_real_connect(&m_lpMySQL, + m_lpConfig->GetSetting("mysql_host"), + m_lpConfig->GetSetting("mysql_user"), + m_lpConfig->GetSetting("mysql_password"), + NULL, + (lpMysqlPort)?atoi(lpMysqlPort):0, + lpMysqlSocket, 0) == NULL) + { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("ECDatabaseMySQL::CreateDatabase(): mysql connect failed"); + goto exit; + } + + if(lpDatabase == NULL) { + ec_log_crit("Unable to create database: Unknown database"); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + + ec_log_notice("Creating database \"%s\"", lpDatabase); + + er = IsInnoDBSupported(); + if(er != erSuccess) + goto exit; + + strQuery = "CREATE DATABASE IF NOT EXISTS `"+std::string(m_lpConfig->GetSetting("mysql_database"))+"`"; + if(Query(strQuery) != erSuccess){ + ec_log_crit("Unable to create database: %s", GetError()); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + + strQuery = "USE `"+std::string(m_lpConfig->GetSetting("mysql_database"))+"`"; + er = DoInsert(strQuery); + if(er != erSuccess) + goto exit; + + // Database tables + for (size_t i = 0; i < ARRAY_SIZE(sDatabaseTables); ++i) { + ec_log_info("Creating table \"%s\"", sDatabaseTables[i].lpComment); + er = DoInsert(sDatabaseTables[i].lpSQL); + if(er != erSuccess) + goto exit; + } + + // Add the default table data + for (size_t i = 0; i < ARRAY_SIZE(sDatabaseData); ++i) { + ec_log_info("Add table data for \"%s\"", sDatabaseData[i].lpComment); + er = DoInsert(sDatabaseData[i].lpSQL); + if(er != erSuccess) + goto exit; + } + + er = InsertServerGUID(this); + if(er != erSuccess) + goto exit; + + // Add the release id in the database + er = UpdateDatabaseVersion(Z_UPDATE_RELEASE_ID); + if(er != erSuccess) + goto exit; + + // Loop throught the update list + for (size_t i = Z_UPDATE_RELEASE_ID; + i < ARRAY_SIZE(sUpdateList); ++i) + { + er = UpdateDatabaseVersion(sUpdateList[i].ulVersion); + if(er != erSuccess) + goto exit; + } + + + ec_log_notice("Database has been created"); + +exit: + return er; +} + +static inline bool row_has_null(DB_ROW row, size_t z) +{ + if (row == NULL) + return true; + while (z-- > 0) + if (row[z] == NULL) + return true; + return false; +} + +ECRESULT ECDatabaseMySQL::GetDatabaseVersion(zcp_versiontuple *dbv) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + bool have_micro; + + /* Check if the "micro" column already exists (it does since v64) */ + er = DoSelect("SELECT databaserevision FROM versions WHERE databaserevision>=64 LIMIT 1", &lpResult); + if (er != erSuccess) + goto exit; + have_micro = GetNumRows(lpResult) > 0; + FreeResult(lpResult); + + strQuery = "SELECT major, minor"; + strQuery += have_micro ? ", micro" : ", 0"; + strQuery += ", revision, databaserevision FROM versions ORDER BY major DESC, minor DESC"; + if (have_micro) + strQuery += ", micro DESC"; + strQuery += ", revision DESC, databaserevision DESC LIMIT 1"; + + er = DoSelect(strQuery, &lpResult); + if(er != erSuccess && mysql_errno(&m_lpMySQL) != ER_NO_SUCH_TABLE) + goto exit; + + if(er != erSuccess || GetNumRows(lpResult) == 0) { + // Ok, maybe < than version 5.10 + // check version + + strQuery = "SHOW COLUMNS FROM properties"; + er = DoSelect(strQuery, &lpResult); + if(er != erSuccess) + goto exit; + + lpDBRow = FetchRow(lpResult); + er = ZARAFA_E_UNKNOWN_DATABASE; + + while (lpDBRow != NULL) { + if (lpDBRow[0] != NULL && stricmp(lpDBRow[0], "storeid") == 0) { + dbv->v_major = 5; + dbv->v_minor = 0; + dbv->v_rev = 0; + dbv->v_schema = 0; + er = erSuccess; + break; + } + lpDBRow = FetchRow(lpResult); + } + + goto exit; + } + + lpDBRow = FetchRow(lpResult); + if (row_has_null(lpDBRow, 5)) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("ECDatabaseMySQL::GetDatabaseVersion(): NULL row or columns"); + goto exit; + } + + dbv->v_major = strtoul(lpDBRow[0], NULL, 0); + dbv->v_minor = strtoul(lpDBRow[1], NULL, 0); + dbv->v_micro = strtoul(lpDBRow[2], NULL, 0); + dbv->v_rev = strtoul(lpDBRow[3], NULL, 0); + dbv->v_schema = strtoul(lpDBRow[4], NULL, 0); + +exit: + if(lpResult) + FreeResult(lpResult); + + return er; +} + +ECRESULT ECDatabaseMySQL::IsUpdateDone(unsigned int ulDatabaseRevision, unsigned int ulRevision) +{ + ECRESULT er = ZARAFA_E_NOT_FOUND; + string strQuery; + DB_RESULT lpResult = NULL; + + strQuery = "SELECT major,minor,revision,databaserevision FROM versions WHERE databaserevision = " + stringify(ulDatabaseRevision); + if (ulRevision > 0) + strQuery += " AND revision = " + stringify(ulRevision); + + strQuery += " ORDER BY major DESC, minor DESC, revision DESC, databaserevision DESC LIMIT 1"; + + er = DoSelect(strQuery, &lpResult); + if(er != erSuccess) + goto exit; + + if(GetNumRows(lpResult) != 1) + er = ZARAFA_E_NOT_FOUND; + +exit: + if(lpResult != NULL) + FreeResult(lpResult); + return er; +} + +ECRESULT ECDatabaseMySQL::GetFirstUpdate(unsigned int *lpulDatabaseRevision) +{ + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + + er = DoSelect("SELECT MIN(databaserevision) FROM versions", &lpResult); + if(er != erSuccess && mysql_errno(&m_lpMySQL) != ER_NO_SUCH_TABLE) + goto exit; + else if(er == erSuccess) + lpDBRow = FetchRow(lpResult); + + er = erSuccess; + + if (lpDBRow == NULL || lpDBRow[0] == NULL ) { + *lpulDatabaseRevision = 0; + }else + *lpulDatabaseRevision = atoui(lpDBRow[0]); + + +exit: + if (lpResult != NULL) + FreeResult(lpResult); + return er; +} + +/** + * Update the database to the current version. + * + * @param[in] bForceUpdate possebly force upgrade + * @param[out] strReport error message + * + * @return Zarafa error code + */ +ECRESULT ECDatabaseMySQL::UpdateDatabase(bool bForceUpdate, std::string &strReport) +{ + ECRESULT er = erSuccess; + bool bUpdated = false; + bool bSkipped = false; + unsigned int ulDatabaseRevisionMin = 0; + zcp_versiontuple stored_ver; + zcp_versiontuple program_ver(PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR, PROJECT_VERSION_MICRO, PROJECT_VERSION_REVISION, Z_UPDATE_LAST); + int cmp; + + er = GetDatabaseVersion(&stored_ver); + if(er != erSuccess) + goto exit; + + + er = GetFirstUpdate(&ulDatabaseRevisionMin); + if(er != erSuccess) + goto exit; + + //default error + strReport = "Unable to upgrade zarafa from version " + + stored_ver.stringify() + " to " + program_ver.stringify(); + + // Check version + cmp = stored_ver.compare(program_ver); + if (cmp == 0 && stored_ver.v_schema == Z_UPDATE_LAST) { + // up to date + goto exit; + } else if (cmp > 0) { + // Start a old server with a new database + strReport = "Database version (" + stored_ver.stringify(',') + + ") is newer than the server version (" + program_ver.stringify(',') + ")"; + er = ZARAFA_E_INVALID_VERSION; + goto exit; + } + + this->m_bForceUpdate = bForceUpdate; + + if (bForceUpdate) + ec_log_warn("Manually forced the database upgrade because the option \"--force-database-upgrade\" was given."); + + // Loop throught the update list + for (size_t i = ulDatabaseRevisionMin; + i < ARRAY_SIZE(sUpdateList); ++i) + { + if ( (ulDatabaseRevisionMin > 0 && IsUpdateDone(sUpdateList[i].ulVersion) == hrSuccess) || + (sUpdateList[i].ulVersionMin != 0 && stored_ver.v_schema >= sUpdateList[i].ulVersion && + stored_ver.v_schema >= sUpdateList[i].ulVersionMin) || + (sUpdateList[i].ulVersionMin != 0 && IsUpdateDone(sUpdateList[i].ulVersionMin, PROJECT_VERSION_REVISION) == hrSuccess)) + { + // Update already done, next + continue; + } + + ec_log_info("Start: %s", sUpdateList[i].lpszLogComment); + + er = Begin(); + if(er != erSuccess) + goto exit; + + bSkipped = false; + er = sUpdateList[i].lpFunction(this); + if (er == ZARAFA_E_IGNORE_ME) { + bSkipped = true; + er = erSuccess; + } else if (er == ZARAFA_E_USER_CANCEL) { + goto exit; // Reason should be logged in the update itself. + } else if (er != hrSuccess) { + Rollback(); + ec_log_err("Failed: Rollback database"); + goto exit; + } + + er = UpdateDatabaseVersion(sUpdateList[i].ulVersion); + if(er != erSuccess) + goto exit; + + er = Commit(); + if(er != erSuccess) + goto exit; + ec_log_notice("%s: %s", bSkipped ? "Skipped" : "Done", sUpdateList[i].lpszLogComment); + bUpdated = true; + } + + // Ok, no changes for the database, but for update history we add a version record + if(bUpdated == false) { + // Update version table + er = UpdateDatabaseVersion(Z_UPDATE_LAST); + if(er != erSuccess) + goto exit; + } + +exit: + + return er; +} + +ECRESULT ECDatabaseMySQL::UpdateDatabaseVersion(unsigned int ulDatabaseRevision) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT result; + bool have_micro; + + /* Check for "micro" column (present in v64+) */ + er = DoSelect("SELECT databaserevision FROM versions WHERE databaserevision>=64 LIMIT 1", &result); + if (er != erSuccess) + goto exit; + have_micro = GetNumRows(result) > 0; + FreeResult(result); + + // Insert version number + strQuery = "INSERT INTO versions (major, minor, "; + if (have_micro) + strQuery += "micro, "; + strQuery += "revision, databaserevision, updatetime) VALUES("; + strQuery += stringify(PROJECT_VERSION_MAJOR) + std::string(", ") + stringify(PROJECT_VERSION_MINOR) + std::string(", "); + if (have_micro) + strQuery += stringify(PROJECT_VERSION_MICRO) + std::string(", "); + strQuery += std::string("'") + std::string(PROJECT_SVN_REV_STR) + std::string("', ") + stringify(ulDatabaseRevision) + ", FROM_UNIXTIME("+stringify(time(NULL))+") )"; + + er = DoInsert(strQuery); + if(er != erSuccess) + goto exit; + +exit: + return er; +} +/** + * Validate all database tables +*/ +ECRESULT ECDatabaseMySQL::ValidateTables() +{ + ECRESULT er = erSuccess; + string strQuery; + list<std::string> listTables; + list<std::string> listErrorTables; + list<std::string>::const_iterator iterTables; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + + er = DoSelect("SHOW TABLES", &lpResult); + if(er != erSuccess) { + ec_log_err("Unable to get all tables from the mysql database. %s", GetError()); + goto exit; + } + + // Get all tables of the database + while( (lpDBRow = FetchRow(lpResult))) { + if (lpDBRow == NULL || lpDBRow[0] == NULL) { + ec_log_err("Wrong table information."); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + + listTables.insert(listTables.end(), lpDBRow[0]); + } + if(lpResult) FreeResult(lpResult); + lpResult = NULL; + + for (iterTables = listTables.begin(); iterTables != listTables.end(); ++iterTables) { + er = DoSelect("CHECK TABLE " + *iterTables, &lpResult); + if(er != erSuccess) { + ec_log_err("Unable to check table \"%s\"", iterTables->c_str()); + goto exit; + } + + lpDBRow = FetchRow(lpResult); + if (lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL) { + ec_log_err("Wrong check table information."); + er = ZARAFA_E_DATABASE_ERROR; + goto exit; + } + + ec_log_info("%30s | %15s | %s", lpDBRow[0], lpDBRow[2], lpDBRow[3]); + if (strcmp(lpDBRow[2], "error") == 0) + listErrorTables.insert(listErrorTables.end(), lpDBRow[0]); + + if(lpResult) FreeResult(lpResult); + lpResult = NULL; + } + + if (!listErrorTables.empty()) + { + ec_log_notice("Rebuilding tables."); +#ifdef HAVE_OFFLINE_SUPPORT + ECDBUpdateProgress *lpProgress = NULL; + unsigned int cUpdate = 0; + + ECDBUpdateProgress::GetInstance(listErrorTables.size(), this, &lpProgress); +#endif + for (iterTables = listErrorTables.begin(); + iterTables != listErrorTables.end(); ++iterTables) { +#ifdef HAVE_OFFLINE_SUPPORT + if (lpProgress) { + er = lpProgress->Start(cUpdate); + if(er != erSuccess) { + ec_log_err("Rebuild of tables canceled by user."); + goto exit; + } + } +#endif + + er = DoUpdate("ALTER TABLE " + *iterTables + " FORCE"); + if(er != erSuccess) { + ec_log_crit("Unable to fix table \"%s\"", iterTables->c_str()); + break; + } +#ifdef HAVE_OFFLINE_SUPPORT + if (lpProgress) + lpProgress->Finish(cUpdate++); +#endif + } +#ifdef HAVE_OFFLINE_SUPPORT + ECDBUpdateProgress::DestroyInstance(); +#endif + if(er != erSuccess) { + ec_log_crit("Rebuild tables failed. Error code 0x%08x", er); + } else { + ec_log_notice("Rebuilding tables done."); + } + }// if (!listErrorTables.empty()) + +exit: + if(lpResult) + FreeResult(lpResult); + + return er; +} diff --git a/ECDatabaseUpdate.cpp b/ECDatabaseUpdate.cpp new file mode 100644 index 000000000000..8fc8bdebc1a9 --- /dev/null +++ b/ECDatabaseUpdate.cpp @@ -0,0 +1,2719 @@ +/* + * Copyright 2005 - 2015 Zarafa B.V. and its licensors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <zarafa/platform.h> + +#include "ECDatabase.h" +#include "ECDatabaseUpdate.h" + +#include <zarafa/stringutil.h> + +#include <zarafa/ECDefs.h> +#include "ECDBDef.h" +#include "ECUserManagement.h" + +#include <zarafa/ecversion.h> + +#include <mapidefs.h> +#include <mapitags.h> +#include "ECConversion.h" +#include "SOAPUtils.h" +#include "ECSearchFolders.h" + +#include "ZarafaICS.h" + +#include <zarafa/charset/convert.h> +#include "ECStringCompat.h" +#include "ECMAPI.h" + +#include <zlib.h> +#include <zarafa/mapiext.h> +#include <edkmdb.h> + +#ifdef HAVE_OFFLINE_SUPPORT +#include "ECDBUpdateProgress.h" + +#define PROGRESS_INIT(_curupdate) \ + ECDBUpdateProgress *__lpProgress = NULL; \ + const unsigned int __ulCurUpdate = (_curupdate); \ + er = ECDBUpdateProgress::GetInstance(Z_UPDATE_CONVERT_NAMES, lpDatabase, &__lpProgress); \ + if (er == erSuccess) \ + er = __lpProgress->Start(__ulCurUpdate); \ + if (er != erSuccess) \ + goto exit; + +#define PROGRESS_DONE \ + er = __lpProgress->Finish(__ulCurUpdate); \ + if (er != erSuccess) \ + goto exit; + +#define INTERMEDIATE_PROGRESS(_cur, _total) \ + er = __lpProgress->SetIntermediateProgress((_cur), (_total)); \ + if (er != erSuccess) \ + goto exit; + +#define INTERMEDIATE_PROGRESS_(_progress) \ + er = __lpProgress->SetIntermediateProgress((_progress)); \ + if (er != erSuccess) \ + goto exit; + +#else + +#define PROGRESS_INIT(...) +#define PROGRESS_DONE +#define INTERMEDIATE_PROGRESS(...) +#define INTERMEDIATE_PROGRESS_(...) + +#endif + +extern int searchfolder_restart_required; // HACK + +/* + Zarafa database upgrade + + Version 4.20 (not include) + * Add table object + * Add table objectproperty + * Add table objectrelation + * Change user table structure + * Converting users and group + + Optional 4.20 / 5.0 (not include) + * Change database engine to INNODB + * Remove key val_string on the table mvproperties and properties + * Change the primary key, "ht" key and "hierarchyid" key on the hierarchy table + * Change externid to field to VARBINARY(255) and add externid key + + Version 4.21 (not include) + * change the "parent" key on the hierarchy table + + Version 5.00 (not included) + * Add column storeid in properties table and update the ids + * Add freebusy folders in public store + * Set the permissions on the free/busy folders + + Version 5.10 + * Add table version + * Add table searchfolders + * Update non-login from 10 to 1 + * (Beta update) Add flags in search table + * rebuild searchfolders + + Version 5.20 + * Create table changes + * Create table syncs + * Create table indexedproperties + * Create table settings + * Insert server guid into settings table + * Create from object id a sourcekeys and add them into indexedproperties + + Version 6.00 + * Create from object id an entryid and add them into indexedproperties + * Update Search criteria + * Update users table to have 'type' field instead of 'isgroup' and 'isnonactive' + * Update users table to have 'signature' field + * Add source_key_auto_increment setting + * Fix unique key in users table + + Version 6.10 + * Add company column + * Add company in user table + * Add company in objectproperty table + + Version 6.20 + * Move public folders and remove favorites + + Version 6.30 + * Add externid column to object table (changed between beta's) + * Add reference table for Single Instance Attachments + * Add distributed lock when upgrading to 6.30 (distributed only on clean 6.30) + * Add abchanges table to hold ab sourcekeys (since they don't fit in the changes table anymore) + * Set tag column in singleinstance to correct tag value (beta's have wrong value) + + Version 6.40 + * Rename object_type columns to objectclass, and fix their contents with the new defined values (in 2 steps) + * Add objectmvproperties table (for offline synced addressbook details) + * Add syncedmessages table for keeping track of messages that were synchronized with a restriction + * Update the primary key on the 'outgoingqueue' table + * Update the primary key on the 'acl' table + * Update externid in users and object table to be blob, so mysql 4.1 leaves trailing 0's + * Update changes table primary key + * Update mvproperties table primary key + * Update objectclass for DB plugin groups to be security enabled + * Update objectrelation table to switch send-as settings + + Version 7.00 + * Print error howto "convert", or if admin forced do the upgrade of tables with text fields to unicode. + * Update stores table to store the username in a char field. + * Update rules xml blobs to unicode. + * Update searchfolder xml blobs to unicode. + + Version 7.0.1 + * update receive folder to unicode and increase the messageclass column size + + Version 7.1.0 + * update WLINK entries to new record key format + + Version independed + * Add setting for IMAP + * Change primary key in changetable and add an extra move key + * Force addressbook resync +*/ + +struct SObject { + SObject(unsigned int id, unsigned int type) {ulId = id; ulType = type;} + bool operator<(const SObject &rhs) const {return (ulId < rhs.ulId || (ulId == rhs.ulId && ulType < rhs.ulType));} + unsigned int ulId; + unsigned int ulType; +}; + +struct SRelation { + SRelation(unsigned int objectId, unsigned int parentObjectId, unsigned int relationType) { + ulObjectId = objectId; ulParentObjectId = parentObjectId; ulRelationType = relationType; + } + unsigned int ulObjectId; + unsigned int ulParentObjectId; + unsigned int ulRelationType; +}; + +// 1 +ECRESULT UpdateDatabaseCreateVersionsTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_VERSIONS); + + return er; +} + +// 2 +ECRESULT UpdateDatabaseCreateSearchFolders(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_SEARCHRESULTS); + + return er; +} + +// 3 +ECRESULT UpdateDatabaseFixUserNonActive(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("UPDATE users SET nonactive=1 WHERE nonactive=10"); + + return er; +} + +// 4 +ECRESULT UpdateDatabaseCreateSearchFoldersFlags(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("ALTER TABLE searchresults ADD COLUMN flags int(11) unsigned NOT NULL default '0'"); + + return er; +} + +// 5 +ECRESULT UpdateDatabasePopulateSearchFolders(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + searchfolder_restart_required = 1; + + return er; +} + +// 6 +ECRESULT UpdateDatabaseCreateChangesTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_CHANGES); + + return er; +} + +// 7 +ECRESULT UpdateDatabaseCreateSyncsTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_SYNCS); + + return er; +} + +// 8 +ECRESULT UpdateDatabaseCreateIndexedPropertiesTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_INDEXED_PROPERTIES); + + return er; +} + +// 9 +ECRESULT UpdateDatabaseCreateSettingsTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_SETTINGS); + + return er; +} + +ECRESULT InsertServerGUID(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + GUID guid; + + if (CoCreateGuid(&guid) != S_OK) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("InsertServerGUID(): CoCreateGuid failed"); + goto exit; + } + + er = lpDatabase->DoInsert("INSERT INTO `settings` VALUES ('server_guid', " + lpDatabase->EscapeBinary((unsigned char *)&guid, sizeof(GUID)) + ")"); + if(er != erSuccess) + goto exit; + +exit: + return er; +} + +// 10 +ECRESULT UpdateDatabaseCreateServerGUID(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = InsertServerGUID(lpDatabase); + + return er; +} + +// 11 +ECRESULT UpdateDatabaseCreateSourceKeys(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLenths = NULL; + + + strQuery = "SELECT `value` FROM `settings` WHERE `name` = 'server_guid'"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if(er != erSuccess) + goto exit; + + lpDBRow = lpDatabase->FetchRow(lpResult); + lpDBLenths = lpDatabase->FetchRowLengths(lpResult); + if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBLenths == NULL || lpDBLenths[0] != sizeof(GUID)) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseCreateSourceKeys(): row or columns NULL"); + goto exit; + } + + //Insert source keys for folders + strQuery = "INSERT INTO indexedproperties (tag, hierarchyid, val_binary) SELECT 26080, h.id, CONCAT(" + lpDatabase->EscapeBinary((unsigned char*)lpDBRow[0], sizeof(GUID)); + strQuery += ", CHAR(h.id&0xFF, h.id>>8&0xFF, h.id>>16&0xFF, h.id>>24&0xFF))"; + strQuery += " FROM hierarchy AS h WHERE h.type = 3"; + + er = lpDatabase->DoInsert(strQuery); + if(er != erSuccess) + goto exit; + + //Insert source keys for messages + strQuery = "INSERT INTO indexedproperties (tag, hierarchyid, val_binary) SELECT 26080, h.id, CONCAT(" + lpDatabase->EscapeBinary((unsigned char*)lpDBRow[0], sizeof(GUID)); + strQuery += ", CHAR(h.id&0xFF, h.id>>8&0xFF, h.id>>16&0xFF, h.id>>24&0xFF))"; + strQuery += " FROM hierarchy AS h LEFT JOIN hierarchy AS p ON h.parent = p.id WHERE h.type = 5 AND p.type = 3"; + + er = lpDatabase->DoInsert(strQuery); + if(er != erSuccess) + goto exit; + +exit: + if(lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 12 +ECRESULT UpdateDatabaseConvertEntryIDs(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLenths = NULL; + int i, nStores; + + strQuery = "SELECT `guid`, `hierarchy_id` FROM `stores`"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if(er != erSuccess) + goto exit; + + nStores = lpDatabase->GetNumRows(lpResult); + + ec_log_notice(" Stores to convert: %d", nStores); + + for (i = 0; i < nStores; ++i) { + lpDBRow = lpDatabase->FetchRow(lpResult); + lpDBLenths = lpDatabase->FetchRowLengths(lpResult); + if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBRow[1] == NULL || + lpDBLenths == NULL || lpDBLenths[0] != sizeof(GUID) ) + { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_crit(" Failed to convert store \"%s\"", (lpDBRow && lpDBRow[1])?lpDBRow[1]:"Unknown"); + + if(lpDBRow == NULL || lpDBRow[0] == NULL || lpDBLenths == NULL || lpDBLenths[0] != sizeof(GUID) ) + ec_log_crit(" The table \"stores\" includes a wrong GUID"); + else + ec_log_crit(" Check the data in table \"stores\""); + + goto exit; + } + + ec_log_notice(" Converting entryids of store %d", atoui(lpDBRow[1])); + + er = CreateRecursiveStoreEntryIds(lpDatabase, atoui(lpDBRow[1]), (unsigned char*)lpDBRow[0]); + if(er != erSuccess) { + ec_log_crit(" Failed to convert store %d", atoui(lpDBRow[1])); + goto exit; + } + + } + +exit: + if(lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +ECRESULT CreateRecursiveStoreEntryIds(ECDatabase *lpDatabase, unsigned int ulStoreHierarchyId, unsigned char* lpStoreGuid) +{ + ECRESULT er = erSuccess; + string strQuery, strInsertQuery, strDefaultQuery; + string strInValues; + DB_RESULT lpDBResult = NULL; + DB_ROW lpDBRow = NULL; + + // FIXME: use ECListInt and ECListIntIterator (in ECGenericObjectTable.h) + std::list<unsigned int> lstFolders; // The list of folders + std::list<unsigned int>::iterator iterFolders; + + // Insert the entryids + strDefaultQuery = "REPLACE INTO indexedproperties (tag, hierarchyid, val_binary) "; + strDefaultQuery+= "SELECT 0x0FFF, h.id, CONCAT('\\0\\0\\0\\0', "+ lpDatabase->EscapeBinary(lpStoreGuid, sizeof(GUID)); + strDefaultQuery+= ", '\\0\\0\\0\\0', CHAR(h.type&0xFF, h.type>>8&0xFF, h.type>>16&0xFF, h.type>>24&0xFF), "; + strDefaultQuery+= "CHAR(h.id&0xFF, h.id>>8&0xFF, h.id>>16&0xFF, h.id>>24&0xFF), '\\0\\0\\0\\0')"; + strDefaultQuery+= " FROM hierarchy AS h WHERE h.id IN "; + + // Add the master id + lstFolders.push_back( ulStoreHierarchyId ); + + iterFolders = lstFolders.begin(); + while (iterFolders != lstFolders.end()) { + + // Make parent list + strInValues.clear(); + + while(iterFolders != lstFolders.end()) { + + strInValues += stringify(*iterFolders); + ++iterFolders; + if (strDefaultQuery.size() + strInValues.size() * 2 > lpDatabase->GetMaxAllowedPacket()) + break; + if(iterFolders != lstFolders.end()) + strInValues += ","; + } + + // Remove the added entries + lstFolders.erase(lstFolders.begin(), iterFolders); + + // Insert the entryids + er = lpDatabase->DoInsert(strDefaultQuery + "(" + strInValues + ")"); + if(er != erSuccess) + goto exit; + + + // Get the new parents + strQuery= "SELECT id FROM hierarchy WHERE parent IN ( "+strInValues+")"; + + er = lpDatabase->DoSelect(strQuery, &lpDBResult); + if (er != erSuccess) + goto exit; + + while(true) { + lpDBRow = lpDatabase->FetchRow(lpDBResult); + + if (lpDBRow == NULL) + break; + + if (lpDBRow[0] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("CreateRecursiveStoreEntryIds(): column is NULL"); + goto exit; + } + + lstFolders.push_back(atoui(lpDBRow[0])); + } + + if (lpDBResult) { + lpDatabase->FreeResult(lpDBResult); + lpDBResult = NULL; + } + iterFolders = lstFolders.begin(); + } //while + +exit: + return er; +} + +// 13 +ECRESULT UpdateDatabaseSearchCriteria(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + DB_RESULT lpDBResult = NULL; + DB_ROW lpDBRow = NULL; + unsigned int ulStoreLast = 0; + unsigned int ulStoreId = 0; + + struct searchCriteria *lpNewSearchCriteria = NULL; + + // Search for all folders with PR_EC_SEARCHCRIT that are not deleted + strQuery = "SELECT properties.storeid, hierarchy.id, properties.val_string FROM hierarchy LEFT JOIN properties ON properties.hierarchyid=hierarchy.id AND properties.tag=" + stringify(PROP_ID(PR_EC_SEARCHCRIT)) +" AND properties.type=" + stringify(PROP_TYPE(PR_EC_SEARCHCRIT)) + " WHERE hierarchy.type=3 AND hierarchy.flags=2 ORDER BY properties.storeid"; + + er = lpDatabase->DoSelect(strQuery, &lpDBResult); + if(er != erSuccess) + goto exit; + + while(true) { + lpDBRow = lpDatabase->FetchRow(lpDBResult); + if (lpDBRow == NULL) + break; + + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL) continue; + + ulStoreId = atoui(lpDBRow[0]); + + if (ulStoreLast != ulStoreId) { + ec_log_notice(" Convert store %d", ulStoreId); + ulStoreLast = ulStoreId; + } + + // convert the entryidlist + if (ConvertSearchCriteria52XTo6XX(lpDatabase, lpDBRow[2], &lpNewSearchCriteria) != erSuccess) + { + ec_log_crit(" WARNING: Unable to convert the search criteria of folder %d", atoui(lpDBRow[1])); + goto next; + } + + // Save criteria in new format + er = ECSearchFolders::SaveSearchCriteria(lpDatabase, atoui(lpDBRow[0]), atoui(lpDBRow[1]), lpNewSearchCriteria); + if (er != erSuccess) { + ec_log_crit(" Unable to save the new search criteria of folder %d", atoui(lpDBRow[1])); + goto exit; + } +next: //Free + if (lpNewSearchCriteria) { + FreeSearchCriteria(lpNewSearchCriteria); + lpNewSearchCriteria = NULL; + } + } + +exit: + + if (lpDBResult) + lpDatabase->FreeResult(lpDBResult); + + if (lpNewSearchCriteria) + FreeSearchCriteria(lpNewSearchCriteria); + + return er; +} + +// 14 +ECRESULT UpdateDatabaseAddUserObjectType(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + /* + * First we create the object_type column and initialize the values + * based on the isgroup and nonactive columns. Once that is done we should + * drop the columns. This will make the users table use the same format as + * the DBUserPlugin which already made use of the object_type column. + */ + er = lpDatabase->DoUpdate("ALTER TABLE users ADD COLUMN object_type int(11) NOT NULL default '0'"); + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("UPDATE users SET object_type=5 WHERE nonactive != 0"); /* USEROBJECT_TYPE_NONACTIVE */ + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("UPDATE users SET object_type=2 WHERE isgroup != 0"); /* USEROBJECT_TYPE_GROUP */ + if(er != erSuccess) + goto exit; + + /* + * All other entries should be considered as users. + * This is safe since there are at this time no other valid object types. + */ + er = lpDatabase->DoUpdate("UPDATE users SET object_type=1 WHERE object_type = 0"); /* USEROBJECT_TYPE_USER */ + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("ALTER TABLE users DROP COLUMN nonactive"); + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("ALTER TABLE users DROP INDEX externid, DROP COLUMN isgroup, ADD INDEX externid (`externid`, `object_type`)"); + if(er != erSuccess) + goto exit; + + /* + * Another change is that for the DB plugin, the 'objects' table should now show type 5 for nonactive users (instead of 1) + */ + + er = lpDatabase->DoUpdate("UPDATE object SET objecttype=5 WHERE id IN (SELECT objectid FROM objectproperty WHERE propname='isnonactive' AND value != 0)"); + if(er != erSuccess) + goto exit; + +exit: + + return er; +} + +// 15 +ECRESULT UpdateDatabaseAddUserSignature(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("ALTER TABLE users ADD COLUMN signature varchar(255) NOT NULL default '0'"); + + return er; +} + +// 16 +ECRESULT UpdateDatabaseAddSourceKeySetting(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("INSERT INTO `settings` VALUES ('source_key_auto_increment' , (SELECT CHAR(MAX(`id`)&0xFF, MAX(`id`)>>8&0xFF, MAX(`id`)>>16&0xFF, MAX(`id`)>>24&0xFF, 0x00, 0x00, 0x00, 0x00) FROM `hierarchy`))"); + + return er; +} + +// 17 +ECRESULT UpdateDatabaseRestrictExternId(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + /* + * The previous upgrade script created an INDEX instead of an UNIQUE INDEX, + * this will result in incorrect behavior when multiple entries with the + * same externid and object_type are inserted. + */ + er = lpDatabase->DoUpdate("ALTER TABLE users DROP INDEX externid, ADD UNIQUE INDEX externid (`externid`, `object_type`)"); + + return er; +} + +// 18 +ECRESULT UpdateDatabaseAddUserCompany(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("ALTER TABLE users ADD COLUMN company int(11) NOT NULL default '0'"); + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoInsert("INSERT INTO `users` (`externid`, `object_type`, `signature`, `company`) VALUES (NULL, 4, '', 0)"); + if(er != erSuccess) + goto exit; +exit: + return er; +} + +// 19 +ECRESULT UpdateDatabaseAddObjectRelationType(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("ALTER TABLE objectrelation ADD COLUMN relationtype tinyint(11) unsigned NOT NULL"); + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("ALTER TABLE objectrelation DROP PRIMARY KEY"); + if(er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("ALTER TABLE objectrelation ADD PRIMARY KEY (`objectid`, `parentobjectid`, `relationtype`)"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("UPDATE objectrelation SET relationtype = " + stringify(OBJECTRELATION_GROUP_MEMBER)); + if (er != erSuccess) + goto exit; + +exit: + + return er; +} + +// 20 +ECRESULT UpdateDatabaseDelUserCompany(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoDelete( + "DELETE FROM `users` " + "WHERE externid IS NULL " + "AND object_type = 4"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoDelete( + "DELETE FROM `objectproperty` " + "WHERE `propname` = 'companyid' " + "AND `value` = 'default'"); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 21 +ECRESULT UpdateDatabaseAddCompanyToStore(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("ALTER TABLE stores ADD COLUMN user_name varbinary(255) NOT NULL default ''"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("ALTER TABLE stores ADD COLUMN company smallint(11) NOT NULL default 0"); + if (er != erSuccess) + goto exit; + + /* + * The user_name column should contain the actual username, but resolving the username for each + * entry will be quite tiresome without much to gain. Instead we just push the userid as + * username and only force the real username for new entries. + * The company column contains the company id to which the user belongs, we can fetch this + * information from the 'users' table. Note that this will always be correct regardless of + * hosted is enabled or disabled since the default value in the 'users' table is 0. + */ + er = lpDatabase->DoUpdate("UPDATE stores SET user_name = user_id, company = IFNULL( (SELECT company FROM users WHERE users.id = user_id), 0)"); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 22 +ECRESULT UpdateDatabaseAddIMAPSequenceNumber(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + + er = lpDatabase->DoSelect("SELECT * FROM settings WHERE name='imapseq'", &lpResult); + if(er != erSuccess) + goto exit; + + if(lpDatabase->GetNumRows(lpResult) == 0) { + er = lpDatabase->DoInsert("INSERT INTO settings (name, value) VALUES('imapseq',(SELECT max(id)+1 FROM hierarchy))"); + if(er != erSuccess) + goto exit; + } + +exit: + if(lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 23 +ECRESULT UpdateDatabaseKeysChanges(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + BOOL bFirst = TRUE; + unsigned int ulRows = 0; + + // Remove duplicates + do { + bFirst = TRUE; + + er = lpDatabase->DoSelect("SELECT id FROM changes GROUP BY parentsourcekey, change_type, sourcekey HAVING COUNT(*) > 1", &lpResult); + if(er != erSuccess) + goto exit; + + ulRows = lpDatabase->GetNumRows(lpResult); + if(ulRows > 0) { + strQuery = "DELETE FROM changes WHERE id IN ("; + while(true) { + lpDBRow = lpDatabase->FetchRow(lpResult); + + if (lpDBRow == NULL) + break; + + if (lpDBRow[0] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseKeysChanges(): column is NULL"); + goto exit; + } + + if (!bFirst) + strQuery += ","; + + bFirst = FALSE; + + strQuery += lpDBRow[0]; + } + strQuery += ")"; + + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + } + + if(lpResult) { + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + } + + }while(ulRows > 0); + + // Change index + er = lpDatabase->DoUpdate("ALTER TABLE changes DROP PRIMARY KEY, DROP KEY `duplicate`, ADD PRIMARY KEY(`parentsourcekey`,`change_type`,`sourcekey`), ADD UNIQUE KEY `changeid` (`id`), ADD KEY `moved` (`moved_from`)"); + if (er != erSuccess) + goto exit; + +exit: + if(lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 24, Move public folders and remove favorites +ECRESULT UpdateDatabaseMoveFoldersInPublicFolder(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + unsigned int ulStoreId = 0; + unsigned int ulSubtreeFolder = 0; + unsigned int ulPublicFolder = 0; + unsigned int ulFavoriteFolder = 0; + unsigned int ulAffRows = 0; + bool bUseStoreAcls = false; + + // Find public stores (For every company) + // Join the subtree, publicfolders and favorites entryid + + strQuery ="SELECT s.hierarchy_id, isub.hierarchyid, ipf.hierarchyid, iff.hierarchyid FROM users AS u " + "JOIN stores AS s ON s.user_id=u.id " + "JOIN properties AS psub ON " + "psub.tag = 0x35E0 AND psub.type = 0x102 AND psub.storeid = s.hierarchy_id " // PR_IPM_SUBTREE_ENTRYID + "JOIN indexedproperties AS isub ON " + "isub.tag=0xFFF AND isub.val_binary = psub.val_binary " + "LEFT JOIN properties AS pf ON " + "pf.tag = 0x6631 AND pf.type = 0x102 AND pf.storeid = s.hierarchy_id " //PR_IPM_PUBLIC_FOLDERS_ENTRYID + "LEFT JOIN indexedproperties AS ipf ON " + "ipf.tag=0xFFF AND ipf.val_binary = pf.val_binary " + "LEFT JOIN properties AS ff ON " + "ff.tag = 0x6630 AND ff.type = 0x102 AND ff.storeid = s.hierarchy_id " //PR_IPM_FAVORITES_ENTRYID + "LEFT JOIN indexedproperties AS iff ON " + "iff.tag=0xFFF AND iff.val_binary = ff.val_binary " + "WHERE u.object_type=4 OR u.id = 1"; // object_type=USEROBJECT_TYPE_COMPANY or id=ZARAFA_UID_EVERYONE + + er = lpDatabase->DoSelect(strQuery, &lpResult); + if(er != erSuccess) + goto exit; + + while(true) { + lpDBRow = lpDatabase->FetchRow(lpResult); + + if (lpDBRow == NULL) + break; + + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL) { + ec_log_crit(" Skip store: Unable to get the store information for storeid \"%s\"", (lpDBRow[0])?lpDBRow[0]:"Unknown"); + continue; + } + + ulAffRows = 0; + bUseStoreAcls = false; + ulStoreId = atoui(lpDBRow[0]); + ulSubtreeFolder = atoui(lpDBRow[1]); + ulPublicFolder = (lpDBRow[2])?atoui(lpDBRow[2]):0; + ulFavoriteFolder = (lpDBRow[3])?atoui(lpDBRow[3]):0; + + if (ulPublicFolder > 0) { + + // Move the publicfolder folders and messages to the subtree + strQuery ="UPDATE hierarchy SET parent="+stringify(ulSubtreeFolder)+" WHERE parent="+stringify(ulPublicFolder); + er = lpDatabase->DoUpdate(strQuery); + if(er != erSuccess) + goto exit; + + // Mark the old folder as deleted + strQuery = "UPDATE hierarchy SET flags=flags|1024 WHERE id="+stringify(ulPublicFolder); + er = lpDatabase->DoUpdate(strQuery); + if(er != erSuccess) + goto exit; + + // Remove acl's from the subtree folder + strQuery = "DELETE FROM acl WHERE hierarchy_id="+stringify(ulSubtreeFolder); + er = lpDatabase->DoDelete(strQuery); + if(er != erSuccess) + goto exit; + + // Move the Public folder acls to the subtree folder + strQuery = "UPDATE acl SET hierarchy_id="+stringify(ulSubtreeFolder)+" WHERE hierarchy_id="+stringify(ulPublicFolder); + er = lpDatabase->DoUpdate(strQuery, &ulAffRows); + if(er != erSuccess) + goto exit; + + if (ulAffRows == 0) + bUseStoreAcls = true; + + } else { + bUseStoreAcls = true; + } + + if (bUseStoreAcls) { + // Move the store acls to the subtree folder + strQuery = "UPDATE acl SET hierarchy_id="+stringify(ulSubtreeFolder)+" WHERE hierarchy_id="+stringify(ulStoreId); + er = lpDatabase->DoUpdate(strQuery); + if(er != erSuccess) + goto exit; + } + + if(ulFavoriteFolder > 0) { + + // Move the favoritefolder folders and messages to the subtree + strQuery ="UPDATE hierarchy SET parent="+stringify(ulSubtreeFolder)+" WHERE parent="+stringify(ulFavoriteFolder); + er = lpDatabase->DoUpdate(strQuery); + if(er != erSuccess) + goto exit; + + // Mark the old folder as deleted + strQuery = "UPDATE hierarchy SET flags=flags|1024 WHERE id="+stringify(ulFavoriteFolder); + er = lpDatabase->DoUpdate(strQuery); + if(er != erSuccess) + goto exit; + + // Remove acl's from the favorite folder + strQuery = "DELETE FROM acl WHERE hierarchy_id="+stringify(ulFavoriteFolder); + er = lpDatabase->DoDelete(strQuery); + if(er != erSuccess) + goto exit; + } + + // Remove acl's from the store + strQuery = "DELETE FROM acl WHERE hierarchy_id="+stringify(ulStoreId); + er = lpDatabase->DoDelete(strQuery); + if(er != erSuccess) + goto exit; + + // Remove the unused properties + strQuery = "DELETE FROM properties " + "WHERE (tag = 0x6631 AND type=0x102 AND storeid = "+stringify(ulStoreId) + ") OR " //PR_IPM_PUBLIC_FOLDERS_ENTRYID + "(tag = 0x6630 AND type = 0x102 AND storeid = "+stringify(ulStoreId) + ")";//PR_IPM_FAVORITES_ENTRYID + + er = lpDatabase->DoUpdate(strQuery); + if(er != erSuccess) + goto exit; + } + + if(lpResult) { + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + } + + +exit: + if(lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 25 +ECRESULT UpdateDatabaseAddExternIdToObject(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLen = NULL; + unsigned int ulNewId = 0; + unsigned int ulNewParentId = 0; + bool bFirstResult; + + std::list<SObject> sObjectList; + std::list<SObject>::const_iterator sObjectIter; + + std::map<SObject,unsigned int> sObjectMap; + std::map<SObject,unsigned int>::const_iterator sObjectMapIter; + + std::list<SRelation> sRelationList; + std::list<SRelation>::const_iterator sRelationIter; + +#define Z_TABLEDEF_OBJECT_R630 "CREATE TABLE object ( \ + `id` int(11) unsigned NOT NULL auto_increment, \ + `externid` varbinary(255), \ + `objecttype` int(11) unsigned NOT NULL default '0', \ + PRIMARY KEY (`id`, `objecttype`), \ + UNIQUE KEY id (`id`), \ + UNIQUE KEY externid (`externid`, `objecttype`) \ + ) ENGINE=InnoDB;" + + // Create the new object table. + strQuery = Z_TABLEDEF_OBJECT_R630; + strQuery.replace(strQuery.find("object"), 6, "object_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + + // Create the new objectproperty table. + strQuery = Z_TABLEDEF_OBJECT_PROPERTY; + strQuery.replace(strQuery.find("objectproperty"), 14, "objectproperty_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + + // Create the new objectrelation table. + strQuery = Z_TABLEDEF_OBJECT_RELATION; + strQuery.replace(strQuery.find("objectrelation"), 14, "objectrelation_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + + // Create a list of all current objects from the object table. + strQuery = "SELECT id, objecttype FROM object ORDER BY objecttype"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err(" object table contains invalid NULL records"); + goto exit; + } + + sObjectList.push_back(SObject(atoi(lpDBRow[0]), atoi(lpDBRow[1]))); + } + + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + + + // Recreate the objects in the object_temp table and on the fly create the queries to regenerate + // their properties in the objectpropert_temp table. + for (sObjectIter = sObjectList.begin(); sObjectIter != sObjectList.end(); ++sObjectIter) { + strQuery = (string)"INSERT INTO object_temp (objecttype, externid) VALUES (" + stringify(sObjectIter->ulType) + ", '" + stringify(sObjectIter->ulId) + "')"; + er = lpDatabase->DoInsert(strQuery, &ulNewId); + if (er != erSuccess) + goto exit; + + // Add to the map for later use + sObjectMap[*sObjectIter] = ulNewId; + + // Find the properties for this object + strQuery = (string)"SELECT propname, value FROM objectproperty WHERE objectid=" + stringify(sObjectIter->ulId); + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + strQuery.clear(); + bFirstResult = true; + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + lpDBLen = lpDatabase->FetchRowLengths(lpResult); + if (lpDBLen == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseAddExternIdToObject(): FetchRowLengths failed"); + goto exit; + } + + if (lpDBRow[0] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseAddExternIdToObject(): column NULL"); + goto exit; + } + + if (strQuery.empty()) + strQuery = "INSERT INTO objectproperty_temp (objectid, propname, value) VALUES "; + + if (!bFirstResult) + strQuery += ","; + else + bFirstResult = false; + + strQuery += + "(" + stringify(ulNewId) + "," + + lpDatabase->EscapeBinary((unsigned char*)lpDBRow[0], lpDBLen[0]) + ","; + + if (lpDBRow[1] == NULL) + strQuery += "NULL)"; + else + strQuery += lpDatabase->EscapeBinary((unsigned char*)lpDBRow[1], lpDBLen[1]) + ")"; + } + + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + + if (!strQuery.empty()) { + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + } + + er = lpDatabase->DoDelete("DELETE FROM objectproperty WHERE objectid=" + stringify(sObjectIter->ulId)); + if (er != erSuccess) + goto exit; + } + + + // Now repopulate the objectrelation table. + strQuery = "SELECT objectid, parentobjectid, relationtype FROM objectrelation"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_crit(" objectrelation table contains invalid NULL records"); + goto exit; + } + + sRelationList.push_back(SRelation(atoi(lpDBRow[0]), atoi(lpDBRow[1]), atoi(lpDBRow[2]))); + } + + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + + strQuery.clear(); + bFirstResult = true; + for (sRelationIter = sRelationList.begin(); sRelationIter != sRelationList.end(); ++sRelationIter) { + // Find the new parentId, if not found: ignore so they disappear .. would have been invalid relations anyway. + switch (sRelationIter->ulRelationType) { + case OBJECTRELATION_QUOTA_USERRECIPIENT: + case OBJECTRELATION_USER_SENDAS: + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulParentObjectId, 1 /* USEROBJECT_TYPE_USER */)); + if (sObjectMapIter == sObjectMap.end()) + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulParentObjectId, 5 /* USEROBJECT_TYPE_NONACTIVE */)); + if (sObjectMapIter == sObjectMap.end()) + continue; + ulNewParentId = sObjectMapIter->second; + break; + + case OBJECTRELATION_GROUP_MEMBER: + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulParentObjectId, 2 /* USEROBJECT_TYPE_GROUP */)); + if (sObjectMapIter == sObjectMap.end()) + continue; + ulNewParentId = sObjectMapIter->second; + break; + + case OBJECTRELATION_COMPANY_VIEW: + case OBJECTRELATION_COMPANY_ADMIN: + case OBJECTRELATION_QUOTA_COMPANYRECIPIENT: + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulParentObjectId, 4 /* USEROBJECT_TYPE_COMPANY */)); + if (sObjectMapIter == sObjectMap.end()) + continue; + ulNewParentId = sObjectMapIter->second; + break; + + case OBJECTRELATION_ADDRESSLIST_MEMBER: + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulParentObjectId, 6 /* USEROBJECT_TYPE_ADDRESSLIST */)); + if (sObjectMapIter == sObjectMap.end()) + continue; + ulNewParentId = sObjectMapIter->second; + break; + } + + // Find the new object id + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulObjectId, 1 /* USEROBJECT_TYPE_USER */)); + if (sObjectMapIter == sObjectMap.end()) + sObjectMapIter = sObjectMap.find(SObject(sRelationIter->ulObjectId, 5)); // USEROBJECT_TYPE_NONACTIVE + if (sObjectMapIter == sObjectMap.end()) + continue; + ulNewId = sObjectMapIter->second; + + // Update strQuery for this relation + if (strQuery.empty()) + strQuery = "INSERT INTO objectrelation_temp (objectid,parentobjectid,relationtype) VALUES "; + + if (!bFirstResult) + strQuery += ","; + else + bFirstResult = false; + + strQuery += "(" + stringify(ulNewId) + "," + stringify(ulNewParentId) + "," + stringify(sRelationIter->ulRelationType) + ")"; + } + + if (!strQuery.empty()) { + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + } + + + + // Now delete the old tables and rename the new ones + er = lpDatabase->DoDelete("DROP TABLE object, objectproperty, objectrelation"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("RENAME TABLE object_temp TO object, " + "objectproperty_temp TO objectproperty, " + "objectrelation_temp TO objectrelation"); + +exit: + // Delete the temporary tables if they exist at this point + lpDatabase->DoDelete("DROP TABLE IF EXISTS object_temp, objectproperty_temp, objectrelation_temp"); + + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 26 +ECRESULT UpdateDatabaseCreateReferences(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + +#ifdef HAVE_OFFLINE_SUPPORT + struct { + const uLong cbUncompressed; + const uLong cbCompressed; + const char *lpCompressed; + } data = { + 8678, + 271, + "\x78\x9c\xed\xd8\xbd\x4a\x03\x41\x14\x86\xe1\x77\x67\x7f\x32\x71" + "\x2d\x82\x24\x82\x36\x26\x60\x63\x15\xc1\xd2\x42\x2c\x2d\x84\x90" + "\xce\x26\xb2\x46\xd1\x45\xd9\x42\xa3\x60\x97\xc2\xab\xf0\x92\xbc" + "\x00\x2f\xc1\x5c\x84\xe0\x38\x1b\x83\x91\x54\x22\xc2\x2a\x7c\x4f" + "\x33\x67\xbf\x81\x39\xa7\xdd\xf3\x16\xd4\x97\x43\x68\x04\xb0\x0d" + "\x13\x5a\xf0\xf4\xb8\x4b\xc9\xec\x50\xf7\xc7\x73\x02\x1d\x66\x26" + "\x2c\xe8\x76\xa1\xbd\x18\x8a\x88\x88\x88\x88\x88\x88\xc8\x9f\x12" + "\xf8\x9f\xff\x25\x7f\x5a\xc2\xf2\x6b\x6c\x60\x9d\x88\x70\x6c\x61" + "\x05\x83\x19\x27\x1f\x85\xeb\xf5\x0f\x0e\xf7\xfb\x47\xae\xea\x89" + "\x7f\xe4\x7f\x4e\x2d\x22\x22\x22\x22\x22\x22\xf2\x4b\x5e\x02\x1a" + "\x55\xcf\x50\xa5\x80\x88\x01\x1b\xbe\x6a\xb1\xf9\x99\xf6\x58\xf5" + "\x37\xa5\x01\x71\x64\x9a\x5b\xed\x6f\xf2\xaf\xa5\x79\x71\x33\xca" + "\x8a\xe1\x59\x7e\x4a\x8c\x1d\x5e\xdc\x16\x97\xbe\x4c\x88\x46\xd9" + "\x39\x35\xd2\xbb\xec\xea\xf8\x24\x2f\xb2\xeb\x7b\xa2\x34\x4d\x99" + "\xae\x5c\xbc\xb0\x03\xb1\x4d\x12\xa6\x1b\x17\xcf\xf8\x20\x89\x7c" + "\x60\xbf\x04\xb5\xd4\xda\x72\x63\xb3\xf6\xd0\x84\xd7\x3d\x70\xf3" + "\x7e\x6e\xd6\xcc\xf9\x4e\x6e\xde\xc6\xf1\x0e\x4f\x19\x36\x95" + }; + Bytef *lpUncompressed = NULL; + uLong cbUncompressed = data.cbUncompressed; + std::string strLobPath; + FILE *fdLob = NULL; +#endif + + er = lpDatabase->DoInsert(Z_TABLEDEF_REFERENCES); + if (er != erSuccess) + goto exit; + + /* + * Create all attachment references from hierarchy table, let + * instanceid be equal to hierarchyid to minimize the impact + * on the upgrade. + */ + strQuery = + "INSERT INTO `singleinstances` (`instanceid`, `hierarchyid`, `tag`) " + "SELECT id, id, " + stringify(PROP_ID(PR_ATTACH_DATA_BIN)) + " " + "FROM `hierarchy` " + "WHERE type = " + stringify(MAPI_ATTACH); + + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + /* We need to rename the column in `lob` */ +#ifdef HAVE_OFFLINE_SUPPORT + lpUncompressed = new Bytef[cbUncompressed]; + + if (uncompress(lpUncompressed, &cbUncompressed, (const Bytef*)data.lpCompressed, data.cbCompressed) != Z_OK) { + er = ZARAFA_E_UNABLE_TO_COMPLETE; + goto exit; + } + + strLobPath = lpDatabase->GetDatabaseDir() + "/zarafa/lob.frm"; + fdLob = fopen(strLobPath.c_str(), "wb"); + if (fdLob == NULL) { + er = ZARAFA_E_UNABLE_TO_COMPLETE; + goto exit; + } + + fwrite(lpUncompressed, 1, cbUncompressed, fdLob); + fclose(fdLob); + + er = lpDatabase->DoUpdate("FLUSH TABLES"); + if (er != erSuccess) + goto exit; +#else + strQuery = + "ALTER TABLE `lob` " + "CHANGE COLUMN `hierarchyid` `instanceid` int(11) unsigned NOT NULL"; + + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; +#endif + +exit: +#ifdef HAVE_OFFLINE_SUPPORT + delete[] lpUncompressed; +#endif + + return er; +} + +// 27 +ECRESULT UpdateDatabaseLockDistributed(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert("INSERT INTO settings VALUES ('lock_distributed_zarafa', 'upgrade')"); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 28 +ECRESULT UpdateDatabaseCreateABChangesTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLen = NULL; + int ulId = 0; + list<int> syncIds; + list<string> queries; + bool fFirst = true; + string strSyncId; + + list<string>::const_iterator queryIter; + list<int>::const_iterator syncIdIter; + + er = lpDatabase->DoInsert(Z_TABLEDEF_ABCHANGES); + if (er != erSuccess) + goto exit; + + strQuery = "SELECT id, sourcekey, parentsourcekey, change_type FROM changes WHERE change_type & " + stringify(ICS_AB) + " AND parentsourcekey=0x00000000AC21A95040D3EE48B319FBA75330442500000000040000000100000000000000"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + // Extract the AB changes from the changes table. + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + lpDBLen = lpDatabase->FetchRowLengths(lpResult); + + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBLen[1] == 0 || lpDBRow[2] == NULL || lpDBLen[2] == 0) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_crit(" changes table contains invalid NULL records"); + goto exit; + } + + ulId = atoi(lpDBRow[0]); + syncIds.push_back(ulId); + + strQuery = "INSERT INTO abchanges (id, sourcekey, parentsourcekey, change_type"; + strQuery += (string)") VALUES (" + lpDBRow[0] + ", " + + lpDatabase->EscapeBinary((unsigned char*)lpDBRow[1], lpDBLen[1]) + ", " + + lpDatabase->EscapeBinary((unsigned char*)lpDBRow[2], lpDBLen[2]) + ", " + + lpDBRow[3]; + strQuery += ")"; + queries.push_back(strQuery); + } + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + + + // Populate the abchanges table with the extracted data + for (queryIter = queries.begin(); queryIter != queries.end(); ++queryIter) { + er = lpDatabase->DoInsert(*queryIter); + if (er != erSuccess) + goto exit; + } + + + // Remove the extracted changes from the changes table + strQuery = "DELETE FROM changes WHERE id IN ("; + for (syncIdIter = syncIds.begin(); syncIdIter != syncIds.end(); ++syncIdIter) { + strSyncId = stringify(*syncIdIter, false); + + if (strQuery.length() + strSyncId.length() + 2 >= lpDatabase->GetMaxAllowedPacket()) { // we need to be able to add a ',' and a ')'; + strQuery += ")"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "DELETE FROM changes WHERE id IN ("; + fFirst = true; + } + + if (!fFirst) + strQuery += ","; + fFirst = false; + + strQuery += strSyncId; + } + if (!fFirst) { + strQuery += ")"; + er = lpDatabase->DoInsert(strQuery); + } + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + if (er != erSuccess) + lpDatabase->DoDelete("DROP TABLE IF EXISTS abchanges"); + + return er; +} + +// 29 +ECRESULT UpdateDatabaseSetSingleinstanceTag(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + + // Force all tag values to PR_ATTACH_DATA_BIN. Up to now, no other values can be present in the table. + strQuery = "UPDATE `singleinstances` SET `tag` = " + stringify(PROP_ID(PR_ATTACH_DATA_BIN)); + + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 30 +ECRESULT UpdateDatabaseCreateSyncedMessagesTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEFS_SYNCEDMESSAGES); + + return er; +} + +// 31 +ECRESULT UpdateDatabaseForceAbResync(ECDatabase *lpDatabase) +{ +#ifdef HAVE_OFFLINE_SUPPORT + ECRESULT er = erSuccess; + std::string strQuery; + + // Remove the PR_EC_AB_SYNC_STATUS property. + strQuery = "DELETE p.* FROM properties AS p JOIN stores AS s ON p.storeid=s.hierarchy_id AND p.hierarchyid=s.hierarchy_id WHERE p.tag=0x67a2 AND p.type=0x102"; + er = lpDatabase->DoDelete(strQuery); +#else + ECRESULT er = ZARAFA_E_IGNORE_ME; +#endif + + return er; +} + +// 32 +ECRESULT UpdateDatabaseRenameObjectTypeToObjectClass(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + // rename columns in users and object tables + strQuery = + "ALTER TABLE `users` " + "CHANGE COLUMN `object_type` `objectclass` int(11) unsigned NOT NULL"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + // Note: type also changes from int to tinyint here + strQuery = + "ALTER TABLE `object` " + "CHANGE COLUMN `objecttype` `objectclass` int(11) unsigned NOT NULL"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 33 +ECRESULT UpdateDatabaseConvertObjectTypeToObjectClass(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLen = NULL; + std::string strQuery, strUpdate; + bool bFirst = true; + std::map<unsigned int, unsigned int> mapTypes; + std::map<unsigned int, unsigned int>::const_iterator iTypes; + std::list<std::string> lstUpdates; + + // make internal SYSTEM a objectclass_t user + er = lpDatabase->DoUpdate("UPDATE `users` SET `objectclass` = "+stringify(ACTIVE_USER)+" WHERE `externid` is NULL AND `objectclass` = 1"); + if (er != erSuccess) + goto exit; + + // make internal EVERYONE a objectclass_t security group + er = lpDatabase->DoUpdate("UPDATE `users` SET `objectclass` = "+stringify(DISTLIST_SECURITY)+" WHERE `externid` is NULL AND `objectclass` = 2"); + if (er != erSuccess) + goto exit; + + // database stored typed, convert to the new objectclass_t values + mapTypes.insert(std::pair<unsigned int, unsigned int>(1, ACTIVE_USER)); // USEROBJECT_TYPE_USER + mapTypes.insert(std::pair<unsigned int, unsigned int>(2, DISTLIST_GROUP)); // USEROBJECT_TYPE_GROUP + mapTypes.insert(std::pair<unsigned int, unsigned int>(3, NONACTIVE_CONTACT)); // USEROBJECT_TYPE_CONTACT (unused, but who knows..) + mapTypes.insert(std::pair<unsigned int, unsigned int>(4, CONTAINER_COMPANY)); // USEROBJECT_TYPE_COMPANY + mapTypes.insert(std::pair<unsigned int, unsigned int>(5, NONACTIVE_USER)); // USEROBJECT_TYPE_NONACTIVE + mapTypes.insert(std::pair<unsigned int, unsigned int>(6, CONTAINER_ADDRESSLIST)); // USEROBJECT_TYPE_ADDRESSLIST + + for (iTypes = mapTypes.begin(); iTypes != mapTypes.end(); ++iTypes) { + // extern id, because it links to object table for DB plugin + // on LDAP plugin, object table is empty. + er = lpDatabase->DoSelect("SELECT `externid`, `objectclass` FROM `users` WHERE `externid` is not NULL AND `objectclass` = "+stringify(iTypes->first), &lpResult); + if (er != erSuccess) + goto exit; + + strUpdate = "("; + bFirst = true; + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + lpDBLen = lpDatabase->FetchRowLengths(lpResult); + if (lpDBRow[0] == NULL || lpDBLen == NULL || lpDBLen[0] == 0) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_crit(" users table contains invalid NULL records for type %d", iTypes->first); + goto exit; + } + + if (!bFirst) + strUpdate += ","; + + strUpdate += lpDatabase->EscapeBinary((unsigned char*)lpDBRow[0], lpDBLen[0]); + bFirst = false; + } + strUpdate += ")"; + + if (bFirst) + continue; // we had no results for this type, continue with the next + + // save all queries in a list, so we don't cross-update types + + strQuery = + "UPDATE `users` SET `objectclass`=" + stringify(iTypes->second) + " " + "WHERE `externid` IN " + strUpdate + " " + "AND `objectclass` = " + stringify(iTypes->first); + lstUpdates.push_back(strQuery); + + strQuery = + "UPDATE `object` SET `objectclass`=" + stringify(iTypes->second) + " " + "WHERE `externid` IN " + strUpdate + " " + "AND `objectclass` = " + stringify(iTypes->first); + lstUpdates.push_back(strQuery); + } + + // process all type updates + for (std::list<std::string>::const_iterator iu = lstUpdates.begin(); + iu != lstUpdates.end(); ++iu) { + er = lpDatabase->DoUpdate(*iu); + if (er != erSuccess) + goto exit; + } + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 34 +ECRESULT UpdateDatabaseAddMVPropertyTable(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoInsert(Z_TABLEDEF_OBJECT_MVPROPERTY); + + return er; +} + +// 35 +ECRESULT UpdateDatabaseCompanyNameToCompanyId(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + string strQuery; + map<string, string> mapIdToName; + std::map<std::string, std::string>::const_iterator iter; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLen = NULL; + + // find all companies + strQuery = "SELECT object.externid, objectproperty.value FROM objectproperty JOIN object ON objectproperty.objectid=object.id WHERE objectproperty.propname = 'companyname'"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL) + continue; + + lpDBLen = lpDatabase->FetchRowLengths(lpResult); + + mapIdToName.insert(pair<string,string>(string(lpDBRow[0], lpDBLen[0]), string(lpDBRow[1], lpDBLen[1]))); + } + + // update objects to link via externid in companyid, not companyname anymore + for (iter = mapIdToName.begin(); iter != mapIdToName.end(); ++iter) { + strQuery = "UPDATE objectproperty SET value = 0x" + bin2hex(iter->first) + + " WHERE propname='companyid' AND value = '" + iter->second + "'"; + + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + } + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 36 +ECRESULT UpdateDatabaseOutgoingQueuePrimarykey(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er= lpDatabase->DoUpdate("ALTER TABLE outgoingqueue DROP PRIMARY KEY, ADD PRIMARY KEY (`hierarchy_id`,`flags`,`store_id`)"); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 37 +ECRESULT UpdateDatabaseACLPrimarykey(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er= lpDatabase->DoUpdate("ALTER TABLE acl DROP PRIMARY KEY, ADD PRIMARY KEY (`hierarchy_id`,`id`,`type`)"); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 38 +ECRESULT UpdateDatabaseBlobExternId(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + strQuery = "ALTER TABLE `object` " + "DROP KEY `externid`, " + "MODIFY `externid` blob, " + "ADD UNIQUE KEY `externid` (`externid`(255), `objectclass`)"; + + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "ALTER TABLE `users` " + "DROP KEY `externid`, " + "MODIFY `externid` blob, " + "ADD UNIQUE KEY `externid` (`externid`(255), `objectclass`)"; + + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 39 +ECRESULT UpdateDatabaseKeysChanges2(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + // Change index + er = lpDatabase->DoUpdate("ALTER TABLE changes DROP PRIMARY KEY, ADD PRIMARY KEY(`parentsourcekey`,`sourcekey`,`change_type`)"); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +/** + * Update the primary key in mvproperties + * + * Change the primary key to get more performance in mysql because the mysql can not + * choose the right key we change the index into one primary key. + * + * @remarks We are checking extra for the 'hi' key because some upgrade issues + * + * @param[in] lpDatabase ECDatabase object pointer to update. + * @retval erSuccess + * Update is done. + * @retval ZARAFA_E_DATABASE_ERROR + * Update failed + */ +// 40 +ECRESULT UpdateDatabaseMVPropertiesPrimarykey(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + bool bUpdate = false; + + er = lpDatabase->DoSelect("SHOW KEYS FROM mvproperties", &lpResult); + if (er != erSuccess) + goto exit; + + // Result: | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL) + continue; + + if(stricmp(lpDBRow[2], "hi") == 0) { + bUpdate = true; + break; + } + } + + if (bUpdate) { + er = lpDatabase->DoUpdate("ALTER TABLE mvproperties DROP PRIMARY KEY, ADD PRIMARY KEY (`hierarchyid`,`tag`,`type`,`orderid`), DROP KEY `hi`"); + if (er != erSuccess) + goto exit; + } + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 41 +ECRESULT UpdateDatabaseFixDBPluginGroups(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + er = lpDatabase->DoUpdate("UPDATE object SET objectclass="+stringify(DISTLIST_SECURITY)+" WHERE objectclass="+stringify(DISTLIST_GROUP)); + if (er != erSuccess) + goto exit; + +exit: + return er; +} + +// 42 +ECRESULT UpdateDatabaseFixDBPluginSendAs(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLen = NULL; + list<std::pair<string, string> > lstRelations; + std::list<std::pair<std::string, std::string> >::const_iterator iRelations; + + // relation 6 == OBJECTRELATION_USER_SENDAS + er = lpDatabase->DoSelect("SELECT objectid, parentobjectid FROM objectrelation WHERE relationtype=6", &lpResult); + if (er != erSuccess) + goto exit; + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL) + continue; + + lpDBLen = lpDatabase->FetchRowLengths(lpResult); + + lstRelations.push_back(pair<string,string>(string(lpDBRow[0], lpDBLen[0]), string(lpDBRow[1], lpDBLen[1]))); + } + + er = lpDatabase->DoDelete("DELETE FROM objectrelation WHERE relationtype=6"); + if (er != erSuccess) + goto exit; + + for (iRelations = lstRelations.begin(); iRelations != lstRelations.end(); ++iRelations) { + er = lpDatabase->DoUpdate("INSERT INTO objectrelation (objectid, parentobjectid, relationtype) VALUES ("+iRelations->second+", "+iRelations->first+", 6)"); + if (er != erSuccess) + goto exit; + } + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +/** + * IMAP used to store subscriptions on the store. This gave problems + * when multi-threaded IMAP clients (Thunderbird) subscribed on + * folders in one thread, and requested the list on another + * thread. This would have returned the old subscribed list, since the + * store doesn't have an update notification system like normal + * folders do. Moved to the Inbox, since this folder is always + * present and easy to find on the server and client. + * + * @param[in] lpDatabase ECDatabase object pointer to update. + * @return ECRESULT erSuccess or ZARAFA_E_DATABASE_ERROR + */ +// 43 +ECRESULT UpdateDatabaseMoveSubscribedList(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + map<string, string> mapStoreInbox; + std::map<std::string, std::string>::const_iterator i; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + DB_LENGTHS lpDBLen = NULL; + + er = lpDatabase->DoSelect("SELECT storeid, objid FROM receivefolder WHERE messageclass='IPM'", &lpResult); + if (er != erSuccess) + goto exit; + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL) + continue; + + lpDBLen = lpDatabase->FetchRowLengths(lpResult); + + mapStoreInbox.insert(pair<string,string>(string(lpDBRow[0], lpDBLen[0]), string(lpDBRow[1], lpDBLen[1]))); + } + + for (i = mapStoreInbox.begin(); i != mapStoreInbox.end(); ++i) { + // Remove property if it's already there (possible if you run new gateway against old server before upgrade) + er = lpDatabase->DoDelete("DELETE FROM properties WHERE storeid="+i->first+" AND hierarchyid="+i->first+" AND tag=0x6784 AND type=0x0102"); + if (er != erSuccess) + goto exit; + + // does not return an error if property was not in the database + er = lpDatabase->DoUpdate("UPDATE properties SET hierarchyid="+i->second+ + " WHERE storeid="+i->first+" AND hierarchyid="+i->first+" AND tag=0x6784 AND type=0x0102"); + if (er != erSuccess) + goto exit; + } + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 44 +ECRESULT UpdateDatabaseSyncTimeIndex(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + bool bHaveIndex; + + // There are upgrade paths where the sync_time key already exists. + er = lpDatabase->CheckExistIndex("syncs", "sync_time", &bHaveIndex); + if (er == erSuccess && !bHaveIndex) + er = lpDatabase->DoUpdate("ALTER TABLE syncs ADD INDEX sync_time (`sync_time`)"); + + return er; +} + +// 45 +ECRESULT UpdateDatabaseAddStateKey(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; +#ifndef HAVE_OFFLINE_SUPPORT + bool bHaveIndex; + + // There are upgrade paths where the state key already exists. + er = lpDatabase->CheckExistIndex("changes", "state", &bHaveIndex); + if (er == erSuccess && !bHaveIndex) + er = lpDatabase->DoUpdate("ALTER TABLE changes ADD UNIQUE KEY `state` (`parentsourcekey`,`id`)"); +#else + + er = ZARAFA_E_IGNORE_ME; +#endif + + return er; +} + +// 46 +ECRESULT UpdateDatabaseConvertToUnicode(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + +#ifndef HAVE_OFFLINE_SUPPORT + if (lpDatabase->m_bForceUpdate) { +#endif + PROGRESS_INIT(Z_UPDATE_CONVERT_TO_UNICODE) + + // Admin requested a forced upgrade, converting known tables + + /* + * Since we inserted the company guid into the objectproperty + * table, this convert may break, since the binary data won't + * be valid utf-8 data in mysql. So we convert the 'companyid' + * properties from this table into a hexed version, which is + * plain text and will not break. + * + * We need to do this first, since the begin/commit will work + * on this statement, and won't on the following alter table + * commands. + */ + strQuery = "UPDATE objectproperty SET value = hex(value) WHERE propname = 'companyid'"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + // Convert tables to unicode + + strQuery = "ALTER TABLE mvproperties MODIFY val_string longtext CHARSET utf8 COLLATE utf8_general_ci"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + // No need to convert the properties table as that will be done on the fly in update 50 (Z_UPDATE_CONVERT_PROPERTIES) + + // db-plugin + strQuery = "ALTER TABLE objectproperty MODIFY propname VARCHAR(255) CHARSET utf8 COLLATE utf8_general_ci, MODIFY value TEXT CHARSET utf8 COLLATE utf8_general_ci"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + /* + * Another similar change is to the SYSADMIN property; it used + * to be 12345:XXXXXXXX with XXXXX being a binary externid. That + * has changed to be a hexed version, so, 12345:HHHHHHHHHH, with + * HHHH being the hexed version of XXXXX + */ + strQuery = "UPDATE objectproperty SET value = concat(substr(value,1,instr(value,';')-1),';',hex(substr(value,instr(value,';')+1))) WHERE propname = 'companyadmin'"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "ALTER TABLE objectmvproperty MODIFY propname VARCHAR(255) CHARSET utf8 COLLATE utf8_general_ci, MODIFY value TEXT CHARSET utf8 COLLATE utf8_general_ci"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + /* + * Other tables containing varchar's are not converted, all data in those fields are us-ascii anyway: + * - receivefolder + * - stores (specially handled in next update + * - settings + */ + PROGRESS_DONE +#ifndef HAVE_OFFLINE_SUPPORT + } else { + ec_log_crit("Will not upgrade your database from 6.40.x to 7.0."); + ec_log_crit("The recommended upgrade procedure is to use the zarafa7-upgrade commandline tool."); + ec_log_crit("Please consult the Zarafa administrator manual on how to correctly upgrade your database."); + ec_log_crit("Alternatively you may try to upgrade using --force-database-upgrade,"); + ec_log_crit("but no progress and estimates within the updates will be available."); + er = ZARAFA_E_USER_CANCEL; + goto exit; + } +#endif + +exit: + return er; +} + +// 47 +ECRESULT UpdateDatabaseConvertStoreUsername(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + PROGRESS_INIT(Z_UPDATE_CONVERT_STORE_USERNAME) + + er = lpDatabase->DoUpdate("UPDATE stores SET user_name = CAST(CONVERT(user_name USING latin1) AS CHAR(255) CHARACTER SET utf8)"); + if (er == erSuccess) + er = lpDatabase->DoUpdate("ALTER TABLE stores MODIFY user_name VARCHAR(255) CHARACTER SET utf8 NOT NULL DEFAULT ''"); + + PROGRESS_DONE + +#ifdef HAVE_OFFLINE_SUPPORT +exit: +#endif + return er; +} + +// 48 +ECRESULT UpdateDatabaseConvertRules(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + + convert_context converter; + char *lpszConverted = NULL; + +#ifdef HAVE_OFFLINE_SUPPORT + unsigned int ulTotal = 0; + unsigned int ulCurrent = 0; +#endif + PROGRESS_INIT(Z_UPDATE_CONVERT_RULES) + + er = lpDatabase->DoSelect("SELECT p.hierarchyid, p.storeid, p.val_binary FROM properties AS p JOIN receivefolder AS r ON p.hierarchyid=r.objid AND p.storeid=r.storeid JOIN stores AS s ON r.storeid=s.hierarchy_id WHERE p.tag=0x3fe1 AND p.type=0x102 AND r.messageclass='IPM'", &lpResult); + if (er != erSuccess) + goto exit; + +#ifdef HAVE_OFFLINE_SUPPORT + ulTotal = lpDatabase->GetNumRows(lpResult); +#endif + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseConvertRules(): column NULL"); + goto exit; + } + + // Use WTF-1252 here since the pre-unicode rule serializer didn't pass the SOAP_C_UTFSTRING flag, causing + // gsoap to encode the data as UTF8, eventhough it was already encoded as WINDOWS-1252. + lpszConverted = ECStringCompat::WTF1252_to_UTF8(NULL, lpDBRow[2], &converter); + + er = lpDatabase->DoUpdate("UPDATE properties SET val_binary='" + lpDatabase->Escape(lpszConverted) + "' WHERE hierarchyid=" + lpDBRow[0] + " AND storeid=" + lpDBRow[1] + " AND tag=0x3fe1 AND type=0x102"); + if (er != erSuccess) + goto exit; + + INTERMEDIATE_PROGRESS(++ulCurrent, ulTotal) + + delete[] lpszConverted; + lpszConverted = NULL; + } + + PROGRESS_DONE + +exit: + delete[] lpszConverted; + + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 49 +ECRESULT UpdateDatabaseConvertSearchFolders(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + + std::string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + + convert_context converter; + char *lpszConverted = NULL; + +#ifdef HAVE_OFFLINE_SUPPORT + unsigned int ulTotal = 0; + unsigned int ulCurrent = 0; +#endif + PROGRESS_INIT(Z_UPDATE_CONVERT_SEARCH_FOLDERS) + + strQuery = "SELECT h.id, p.storeid, p.val_string FROM hierarchy AS h JOIN properties AS p ON p.hierarchyid=h.id AND p.tag=" + stringify(PROP_ID(PR_EC_SEARCHCRIT)) +" AND p.type=" + stringify(PROP_TYPE(PR_EC_SEARCHCRIT)) + " WHERE h.type=3 AND h.flags=2"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + +#ifdef HAVE_OFFLINE_SUPPORT + ulTotal = lpDatabase->GetNumRows(lpResult); +#endif + + while ((lpDBRow = lpDatabase->FetchRow(lpResult))) { + if (lpDBRow[0] == NULL || lpDBRow[1] == NULL || lpDBRow[2] == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseConvertSearchFolders(): column NULL"); + goto exit; + } + + // Use WTF-1252 here since the pre-unicode rule serializer didn't pass the SOAP_C_UTFSTRING flag, causing + // gsoap to encode the data as UTF8, eventhough it was already encoded as WINDOWS-1252. + lpszConverted = ECStringCompat::WTF1252_to_WINDOWS1252(NULL, lpDBRow[2], &converter); + + er = lpDatabase->DoUpdate("UPDATE properties SET val_string='" + lpDatabase->Escape(lpszConverted) + "' WHERE hierarchyid=" + lpDBRow[0] + " AND storeid=" + lpDBRow[1] + " AND tag=" + stringify(PROP_ID(PR_EC_SEARCHCRIT)) +" AND type=" + stringify(PROP_TYPE(PR_EC_SEARCHCRIT))); + if (er != erSuccess) + goto exit; + + INTERMEDIATE_PROGRESS(++ulCurrent, ulTotal) + + delete[] lpszConverted; + lpszConverted = NULL; + } + + PROGRESS_DONE + +exit: + delete[] lpszConverted; + + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 50 +ECRESULT UpdateDatabaseConvertProperties(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + +#ifdef HAVE_OFFLINE_SUPPORT + unsigned int ulTotal = 0; + unsigned int ulCurrent = 0; +#endif + PROGRESS_INIT(Z_UPDATE_CONVERT_PROPERTIES) + + // Create the temporary properties table + strQuery = Z_TABLEDEF_PROPERTIES; + strQuery.replace(strQuery.find("CREATE TABLE"), strlen("CREATE TABLE"), "CREATE TABLE IF NOT EXISTS"); + strQuery.replace(strQuery.find("properties"), strlen("properties"), "properties_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + +#ifdef HAVE_OFFLINE_SUPPORT + strQuery = "SELECT MAX(hierarchyid) FROM properties"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + lpDBRow = lpDatabase->FetchRow(lpResult); + if (lpDBRow == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseConvertProperties(): row non existing"); + goto exit; + } + + ulTotal = lpDBRow[0] ? atoui(lpDBRow[0]) : 0; + lpDatabase->FreeResult(lpResult); + lpResult = NULL; +#endif + + while (true) { + strQuery = "INSERT IGNORE INTO properties_temp (hierarchyid,tag,type,val_ulong,val_string,val_binary,val_double,val_longint,val_hi,val_lo) SELECT hierarchyid,tag,type,val_ulong,val_string,val_binary,val_double,val_longint,val_hi,val_lo FROM properties ORDER BY hierarchyid ASC LIMIT 10000"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "DELETE FROM properties ORDER BY hierarchyid ASC LIMIT 10000"; + er = lpDatabase->DoDelete(strQuery); + if (er != erSuccess) + goto exit; + + er = lpDatabase->Commit(); + if (er != erSuccess) + goto exit; + + er = lpDatabase->Begin(); + if (er != erSuccess) + goto exit; + + strQuery = "SELECT MIN(hierarchyid) FROM properties"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + lpDBRow = lpDatabase->FetchRow(lpResult); + if (lpDBRow == NULL || lpDBRow[0] == NULL) + break; + + INTERMEDIATE_PROGRESS(atoui(lpDBRow[0]), ulTotal) + + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + } + + // update webaccess settings which were already utf8 in our latin1 table + strQuery = "UPDATE properties_temp JOIN hierarchy ON properties_temp.hierarchyid=hierarchy.id AND hierarchy.parent IS NULL SET val_string = CAST(CAST(CONVERT(val_string USING latin1) AS binary) AS CHAR CHARACTER SET utf8) WHERE properties_temp.type=0x1e AND properties_temp.tag=26480"; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("RENAME TABLE properties TO properties_old, properties_temp TO properties"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoDelete("DROP TABLE properties_old"); + + PROGRESS_DONE + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + + return er; +} + +// 51 +ECRESULT UpdateDatabaseCreateCounters(ECDatabase *lpDatabase) +{ + const struct { + ULONG ulPropTag; + ULONG ulChildType; + ULONG ulChildFlagMask; + ULONG ulChildFlags; + const char* lpszValue; + } counter_info[] = { + { PR_CONTENT_COUNT, MAPI_MESSAGE, MAPI_ASSOCIATED|MSGFLAG_DELETED, 0, "COUNT(*)" }, + { PR_CONTENT_UNREAD, MAPI_MESSAGE, MAPI_ASSOCIATED|MSGFLAG_DELETED|MSGFLAG_READ, 0, "SUM(IF(flags&1,0,1))" }, + { PR_ASSOC_CONTENT_COUNT, MAPI_MESSAGE, MAPI_ASSOCIATED|MSGFLAG_DELETED, MAPI_ASSOCIATED, "0" }, + { PR_DELETED_MSG_COUNT, MAPI_MESSAGE, MAPI_ASSOCIATED|MSGFLAG_DELETED, MSGFLAG_DELETED, "0" }, + { PR_DELETED_ASSOC_MSG_COUNT, MAPI_MESSAGE, MAPI_ASSOCIATED|MSGFLAG_DELETED, MAPI_ASSOCIATED|MSGFLAG_DELETED, "0" }, + { PR_SUBFOLDERS, MAPI_FOLDER, MSGFLAG_DELETED, 0, "0" }, + { PR_FOLDER_CHILD_COUNT, MAPI_FOLDER, MSGFLAG_DELETED, 0, "0" }, + { PR_DELETED_FOLDER_COUNT, MAPI_FOLDER, MSGFLAG_DELETED, MSGFLAG_DELETED, "0" } + }; + + ECRESULT er = erSuccess; + std::string strQuery; + +#ifdef HAVE_OFFLINE_SUPPORT + unsigned int ulCurrent = 0; +#endif + PROGRESS_INIT(Z_UPDATE_CREATE_COUNTERS) + + for (unsigned i = 0; i < 8; ++i) { + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_ulong) " + "SELECT parent.id,"+stringify(PROP_ID(counter_info[i].ulPropTag))+","+stringify(PROP_TYPE(counter_info[i].ulPropTag))+",count(child.id) " + "FROM hierarchy AS parent " + "LEFT JOIN hierarchy AS child ON parent.id=child.parent AND " + "parent.type=3 and child.type="+stringify(counter_info[i].ulChildType)+" AND " + "child.flags & "+stringify(counter_info[i].ulChildFlagMask)+"="+stringify(counter_info[i].ulChildFlags)+" " + "GROUP BY parent.id"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + INTERMEDIATE_PROGRESS(++ulCurrent, 16) + + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_ulong) " + "SELECT folderid,"+stringify(PROP_ID(counter_info[i].ulPropTag))+","+stringify(PROP_TYPE(counter_info[i].ulPropTag))+","+counter_info[i].lpszValue+" FROM searchresults GROUP BY folderid"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + INTERMEDIATE_PROGRESS(++ulCurrent, 16) + } + + PROGRESS_DONE + +exit: + return er; +} + +// 52 +ECRESULT UpdateDatabaseCreateCommonProps(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + PROGRESS_INIT(Z_UPDATE_CREATE_COMMON_PROPS) + + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_hi,val_lo,val_ulong) " + "SELECT h.id,"+stringify(PROP_ID(PR_CREATION_TIME))+","+stringify(PROP_TYPE(PR_CREATION_TIME))+",(UNIX_TIMESTAMP(h.createtime) * 10000000 + 116444736000000000) >> 32,(UNIX_TIMESTAMP(h.createtime) * 10000000 + 116444736000000000) & 0xffffffff, NULL " + "FROM hierarchy AS h " + "WHERE h.type IN (3,5,7)"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + INTERMEDIATE_PROGRESS_(.25) + + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_hi,val_lo,val_ulong) " + "SELECT h.id,"+stringify(PROP_ID(PR_LAST_MODIFICATION_TIME))+","+stringify(PROP_TYPE(PR_LAST_MODIFICATION_TIME))+",(UNIX_TIMESTAMP(h.modtime) * 10000000 + 116444736000000000) >> 32,(UNIX_TIMESTAMP(h.modtime) * 10000000 + 116444736000000000) & 0xffffffff, NULL " + "FROM hierarchy AS h " + "WHERE h.type IN (3,5,7)"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + INTERMEDIATE_PROGRESS_(.5) + + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_hi,val_lo,val_ulong) " + "SELECT h.id,"+stringify(PROP_ID(PR_MESSAGE_FLAGS))+","+stringify(PROP_TYPE(PR_MESSAGE_FLAGS))+",NULL, NULL, h.flags " + "FROM hierarchy AS h " + "WHERE h.type IN (3,5,7)"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + INTERMEDIATE_PROGRESS_(.75) + + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_hi,val_lo,val_ulong) " + "SELECT h.id,"+stringify(PROP_ID(PR_FOLDER_TYPE))+","+stringify(PROP_TYPE(PR_FOLDER_TYPE))+",NULL, NULL, h.flags & 0x3 " + "FROM hierarchy AS h " + "WHERE h.type=3"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + INTERMEDIATE_PROGRESS_(1) + + PROGRESS_DONE + +exit: + return er; +} + +// 53 +ECRESULT UpdateDatabaseCheckAttachments(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + PROGRESS_INIT(Z_UPDATE_CHECK_ATTACHMENTS) + + strQuery = "REPLACE INTO properties(hierarchyid,tag,type,val_ulong) " + "SELECT h.id,"+stringify(PROP_ID(PR_HASATTACH))+","+stringify(PROP_TYPE(PR_HASATTACH))+",IF(att.id,1,0) " + "FROM hierarchy AS h " + "LEFT JOIN hierarchy AS att ON h.id=att.parent AND att.type=7 AND h.type=5 " + "GROUP BY h.id"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + INTERMEDIATE_PROGRESS_(.5) + + strQuery = "UPDATE properties AS p " + "JOIN hierarchy AS h ON p.hierarchyid=h.id AND h.type=5 " + "LEFT JOIN hierarchy AS c ON c.type=7 AND c.parent=p.hierarchyid " + "SET p.val_ulong = IF(c.id,p.val_ulong|"+stringify(MSGFLAG_DELETED)+", p.val_ulong & ~"+stringify(MSGFLAG_DELETED)+") " + "WHERE p.tag="+stringify(PROP_ID(PR_MESSAGE_FLAGS))+" AND p.type="+stringify(PROP_TYPE(PR_MESSAGE_FLAGS)); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + INTERMEDIATE_PROGRESS_(1) + + PROGRESS_DONE + +exit: + return er; +} + +// 54 +ECRESULT UpdateDatabaseCreateTProperties(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + +#ifndef HAVE_OFFLINE_SUPPORT + // Create the tproperties table + er = lpDatabase->DoInsert(Z_TABLEDEF_TPROPERTIES); + if (er != erSuccess) + goto exit; + + strQuery = "INSERT IGNORE INTO tproperties (folderid,hierarchyid,tag,type,val_ulong,val_string,val_binary,val_double,val_longint,val_hi,val_lo) " + "SELECT h.id, p.hierarchyid, p.tag, p.type, p.val_ulong, LEFT(p.val_string,255), LEFT(p.val_binary,255), p.val_double, p.val_longint, p.val_hi, p.val_lo " + "FROM properties AS p " + "JOIN hierarchy AS tmp ON p.hierarchyid = tmp.id AND p.tag NOT IN (" + stringify(PROP_ID(PR_BODY_HTML)) + "," + stringify(PROP_ID(PR_RTF_COMPRESSED)) + ")" + "LEFT JOIN hierarchy AS h ON tmp.parent = h.id AND h.type = 3"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; +#else + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + unsigned int ulTotal = 0; + unsigned int ulCurrent = 0; + + PROGRESS_INIT(Z_UPDATE_CREATE_TPROPERTIES) + + // Create the tproperties table + strQuery = Z_TABLEDEF_TPROPERTIES; + strQuery.replace(strQuery.find("CREATE TABLE"), strlen("CREATE TABLE"), "CREATE TABLE IF NOT EXISTS"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "SELECT MAX(hierarchyid) FROM properties"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + lpDBRow = lpDatabase->FetchRow(lpResult); + if (lpDBRow == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseCreateTProperties(): row non existing"); + goto exit; + } + + ulTotal = lpDBRow[0] ? atoui(lpDBRow[0]) : 0; + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + + while (ulCurrent <= ulTotal) { + unsigned int ulInserted, ulAffected; + + strQuery = "INSERT IGNORE INTO tproperties (folderid,hierarchyid,tag,type,val_ulong,val_string,val_binary,val_double,val_longint,val_hi,val_lo) " + "SELECT h.id, p.hierarchyid, p.tag, p.type, p.val_ulong, LEFT(p.val_string,255), LEFT(p.val_binary,255), p.val_double, p.val_longint, p.val_hi, p.val_lo " + "FROM properties AS p " + "JOIN hierarchy AS tmp ON p.hierarchyid = tmp.id AND p.tag NOT IN (" + stringify(PROP_ID(PR_BODY_HTML)) + "," + stringify(PROP_ID(PR_RTF_COMPRESSED)) + ") " + " AND p.hierarchyid >= " + stringify(ulCurrent) + " AND p.hierarchyid < " + stringify(ulCurrent + 500) + " " + "LEFT JOIN hierarchy AS h ON tmp.parent = h.id AND h.type = 3"; + er = lpDatabase->DoInsert(strQuery, &ulInserted, &ulAffected); + if (er != erSuccess) + goto exit; + + ulCurrent += 500; + INTERMEDIATE_PROGRESS(ulCurrent, ulTotal) + } + + PROGRESS_DONE +#endif + +exit: +#ifdef HAVE_OFFLINE_SUPPORT + if (lpResult) + lpDatabase->FreeResult(lpResult); +#endif + + return er; +} + +// 55 +ECRESULT UpdateDatabaseConvertHierarchy(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + PROGRESS_INIT(Z_UPDATE_CONVERT_HIERARCHY) + + // Create the temporary properties table + strQuery = Z_TABLEDEF_HIERARCHY; + strQuery.replace(strQuery.find("hierarchy"), strlen("hierarchy"), "hierarchy_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + // Folders can be 0, 1, 2 and 0x400 (Deleted) + // Messages can be 0x40 (associated) and Deleted + // Other can be Deleted + strQuery = "INSERT INTO hierarchy_temp (id, parent, type, flags, owner) SELECT id, parent, type, CASE type WHEN 3 THEN flags & 0x403 WHEN 5 THEN flags & 0x440 ELSE flags & 0x400 END, owner FROM hierarchy"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("RENAME TABLE hierarchy TO hierarchy_old, hierarchy_temp TO hierarchy"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoDelete("DROP TABLE hierarchy_old"); + + PROGRESS_DONE + +exit: + lpDatabase->DoDelete("DROP TABLE IF EXISTS hierarchy_temp"); + + return er; +} + +// 56 +ECRESULT UpdateDatabaseCreateDeferred(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + PROGRESS_INIT(Z_UPDATE_CREATE_DEFERRED) + + // Create the deferred table + er = lpDatabase->DoInsert(Z_TABLEDEF_DELAYEDUPDATE); + + PROGRESS_DONE + +#ifdef HAVE_OFFLINE_SUPPORT +exit: +#endif + return er; +} + +// 57 +ECRESULT UpdateDatabaseConvertChanges(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; +#ifndef HAVE_OFFLINE_SUPPORT + bool bDropColumn; + + // In some upgrade paths the moved_from column doesn't exist. We'll + // check so no error (which we could ignore) will be logged. + er = lpDatabase->CheckExistColumn("changes", "moved_from", &bDropColumn); + if (er == erSuccess && bDropColumn) { + strQuery = "ALTER TABLE changes DROP COLUMN moved_from, DROP key moved"; + er = lpDatabase->DoDelete(strQuery); + } + +#else + DB_RESULT lpResult = NULL; + DB_ROW lpDBRow = NULL; + unsigned int ulTotal = 0; + unsigned int ulCurrent = 0; + + PROGRESS_INIT(Z_UPDATE_CONVERT_CHANGES) + + // Create the temporary properties table + strQuery = Z_TABLEDEF_CHANGES; + strQuery.replace(strQuery.find("CREATE TABLE"), strlen("CREATE TABLE"), "CREATE TABLE IF NOT EXISTS"); + strQuery.replace(strQuery.find("changes"), strlen("changes"), "changes_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "SELECT MAX(id) FROM changes"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + lpDBRow = lpDatabase->FetchRow(lpResult); + if (lpDBRow == NULL) { + er = ZARAFA_E_DATABASE_ERROR; + ec_log_err("UpdateDatabaseConvertChanges(): row non existing"); + goto exit; + } + + ulTotal = lpDBRow[0] ? atoui(lpDBRow[0]) : 0; + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + + while (true) { + strQuery = "INSERT INTO changes_temp (id, sourcekey, parentsourcekey, change_type, flags, sourcesync) SELECT id, sourcekey, parentsourcekey, change_type, flags, sourcesync FROM changes ORDER BY id ASC LIMIT 10000"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "DELETE FROM changes ORDER BY id ASC LIMIT 10000"; + er = lpDatabase->DoDelete(strQuery); + if (er != erSuccess) + goto exit; + + er = lpDatabase->Commit(); + if (er != erSuccess) + goto exit; + + er = lpDatabase->Begin(); + if (er != erSuccess) + goto exit; + + strQuery = "SELECT MIN(id) FROM changes"; + er = lpDatabase->DoSelect(strQuery, &lpResult); + if (er != erSuccess) + goto exit; + + lpDBRow = lpDatabase->FetchRow(lpResult); + if (lpDBRow == NULL || lpDBRow[0] == NULL) + break; + + INTERMEDIATE_PROGRESS(atoui(lpDBRow[0]), ulTotal) + lpDatabase->FreeResult(lpResult); + lpResult = NULL; + } + + er = lpDatabase->DoUpdate("RENAME TABLE changes TO changes_old, changes_temp TO changes"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoDelete("DROP TABLE changes_old"); + + PROGRESS_DONE + +exit: + if (lpResult) + lpDatabase->FreeResult(lpResult); + +#endif + return er; +} + +// 58 +ECRESULT UpdateDatabaseConvertNames(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + PROGRESS_INIT(Z_UPDATE_CONVERT_NAMES) + + // CharsetDetect(names) + + // Create the temporary names table + strQuery = Z_TABLEDEF_NAMES; + strQuery.replace(strQuery.find("names"), strlen("names"), "names_temp"); + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + strQuery = "INSERT INTO names_temp (id,nameid,namestring,guid) SELECT id,nameid,CAST(CAST(CONVERT(namestring USING latin1) AS binary) AS CHAR CHARACTER SET utf8),guid FROM names"; + er = lpDatabase->DoInsert(strQuery); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoUpdate("RENAME TABLE names TO names_old, names_temp TO names"); + if (er != erSuccess) + goto exit; + + er = lpDatabase->DoDelete("DROP TABLE names_old"); + + PROGRESS_DONE + +exit: + lpDatabase->DoDelete("DROP TABLE IF EXISTS names_temp"); + + return er; +} + +// 59 +ECRESULT UpdateDatabaseReceiveFolderToUnicode(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + strQuery = "ALTER TABLE receivefolder MODIFY messageclass varchar(255) CHARSET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT''"; + + er = lpDatabase->DoUpdate(strQuery); + + return er; +} + +// 60 +ECRESULT UpdateDatabaseClientUpdateStatus(ECDatabase *lpDatabase) +{ + return lpDatabase->DoInsert(Z_TABLEDEF_CLIENTUPDATESTATUS); +} + +// 61 +ECRESULT UpdateDatabaseConvertStores(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + // user_hierarchy_id does not exist on all servers, depends on upgrade path + strQuery = "ALTER TABLE stores " + "DROP KEY `user_hierarchy_id` "; + er = lpDatabase->DoUpdate(strQuery); + if (er != erSuccess) { + ec_log_err("Ignoring optional index error, and continuing database upgrade"); + er = erSuccess; + } + + strQuery = "ALTER TABLE stores " + "DROP PRIMARY KEY, " + "ADD COLUMN `type` smallint(6) unsigned NOT NULL default '0', " + "ADD PRIMARY KEY (`user_id`, `hierarchy_id`, `type`), " + "ADD UNIQUE KEY `id` (`id`)"; + er = lpDatabase->DoUpdate(strQuery); + + return er; +} + +// 62 +ECRESULT UpdateDatabaseUpdateStores(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + strQuery = "UPDATE stores SET type="+stringify(ECSTORE_TYPE_PUBLIC)+" WHERE user_id=1 OR user_id IN (SELECT id FROM users where objectclass="+stringify(CONTAINER_COMPANY)+")"; + er = lpDatabase->DoUpdate(strQuery); + + return er; +} + +// 63 +ECRESULT UpdateWLinkRecordKeys(ECDatabase *lpDatabase) +{ + ECRESULT er = erSuccess; + std::string strQuery; + + strQuery = "update stores " // For each store + "join properties as p1 on p1.tag = 0x35E6 and p1.hierarchyid=stores.hierarchy_id " // Get PR_COMMON_VIEWS_ENTRYID + "join indexedproperties as i1 on i1.val_binary = p1.val_binary and i1.tag=0xfff " // Get hierarchy for common views + "join hierarchy as h2 on h2.parent=i1.hierarchyid " // Get children of common views + "join properties as p2 on p2.hierarchyid=h2.id and p2.tag=0x684d " // Get PR_WLINK_RECKEY for each child + "join properties as p3 on p3.hierarchyid=h2.id and p3.tag=0x684c " // Get PR_WLINK_ENTRYID for each child + "set p2.val_binary = p3.val_binary " // Set PR_WLINK_RECKEY = PR_WLINK_ENTRYID + "where length(p3.val_binary) = 48"; // Where entryid length is 48 (zarafa) + er = lpDatabase->DoUpdate(strQuery); + + return er; +} + +/* Edit no. 64 */ +ECRESULT UpdateVersionsTbl(ECDatabase *db) +{ + return db->DoUpdate( + "alter table `versions` " + "add column `micro` int(11) unsigned not null default 0 after `minor`, " + "drop primary key, " + "add primary key (`major`, `minor`, `micro`, `revision`, `databaserevision`)"); +} + +/* Edit no. 65 */ +ECRESULT UpdateChangesTbl(ECDatabase *db) +{ + return db->DoUpdate( + "alter table `changes` " + "modify change_type int(11) unsigned not null default 0"); +} + +/* Edit no. 66 */ +ECRESULT UpdateABChangesTbl(ECDatabase *db) +{ + return db->DoUpdate( + "alter table `abchanges` " + "modify change_type int(11) unsigned not null default 0"); +} diff --git a/ECDatabaseUpdate.h b/ECDatabaseUpdate.h new file mode 100644 index 000000000000..ec47c8fd6bc0 --- /dev/null +++ b/ECDatabaseUpdate.h @@ -0,0 +1,109 @@ +/* + * Copyright 2005 - 2015 Zarafa B.V. and its licensors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#ifndef ECDATABASEUPDATE_H +#define ECDATABASEUPDATE_H + +#include <zarafa/ECLogger.h> + +ECRESULT UpdateDatabaseCreateVersionsTable(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateSearchFolders(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseFixUserNonActive(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateSearchFoldersFlags(ECDatabase *lpDatabase); +ECRESULT UpdateDatabasePopulateSearchFolders(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseCreateChangesTable(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateSyncsTable(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateIndexedPropertiesTable(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateSettingsTable(ECDatabase *lpDatabase); +ECRESULT InsertServerGUID(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateServerGUID(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateSourceKeys(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseConvertEntryIDs(ECDatabase *lpDatabase); +ECRESULT CreateRecursiveStoreEntryIds(ECDatabase *lpDatabase, unsigned int ulStoreHierarchyId, unsigned char* lpStoreGuid); +ECRESULT UpdateDatabaseSearchCriteria(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddUserObjectType(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddUserSignature(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddSourceKeySetting(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseRestrictExternId(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseAddUserCompany(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddObjectRelationType(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseDelUserCompany(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddCompanyToStore(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseAddIMAPSequenceNumber(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseKeysChanges(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseMoveFoldersInPublicFolder(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseAddExternIdToObject(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateReferences(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseLockDistributed(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateABChangesTable(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseSetSingleinstanceTag(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseCreateSyncedMessagesTable(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseForceAbResync(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseRenameObjectTypeToObjectClass(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertObjectTypeToObjectClass(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddMVPropertyTable(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCompanyNameToCompanyId(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseOutgoingQueuePrimarykey(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseACLPrimarykey(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseBlobExternId(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseKeysChanges2(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseMVPropertiesPrimarykey(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseFixDBPluginGroups(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseFixDBPluginSendAs(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseMoveSubscribedList(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseSyncTimeIndex(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseAddStateKey(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseConvertToUnicode(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseConvertStoreUsername(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertRules(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertSearchFolders(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseConvertProperties(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateCounters(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateCommonProps(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCheckAttachments(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateTProperties(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertHierarchy(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseCreateDeferred(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertChanges(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertNames(ECDatabase *lpDatabase); + +ECRESULT UpdateDatabaseReceiveFolderToUnicode(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseClientUpdateStatus(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseConvertStores(ECDatabase *lpDatabase); +ECRESULT UpdateDatabaseUpdateStores(ECDatabase *lpDatabase); + +ECRESULT UpdateWLinkRecordKeys(ECDatabase *lpDatabase); +ECRESULT UpdateVersionsTbl(ECDatabase *db); +ECRESULT UpdateChangesTbl(ECDatabase *db); +ECRESULT UpdateABChangesTbl(ECDatabase *db); + +#endif // #ifndef ECDATABASEUPDATE_H @@ -7,7 +7,7 @@ groups=('zarafa' replaces=('zarafa-server-arm') pkgver=7.2.4.29 _pkgmajver=7.2 -pkgrel=112 +pkgrel=113 pkgdesc="Open Source Groupware Solution" arch=('armv7h' 'armv6h' @@ -132,15 +132,27 @@ source=("https://download.zarafa.com/community/final/${_pkgmajver}/${pkgver}/zcp 'zarafa-tools::git+https://github.com/zarafagroupware/zarafa-tools.git' 'python-zarafa::git+https://github.com/zarafagroupware/python-zarafa.git' 'zarafa-inspector::git+https://github.com/zarafagroupware/zarafa-inspector.git' - 'zarafa-pietma::git+https://git.pietma.com/pietma/com-pietma-zarafa.git#tag=v0.13') + 'zarafa-pietma::git+https://git.pietma.com/pietma/com-pietma-zarafa.git#tag=v0.13' + 'ECDBDef.h' + 'ECDatabaseMySQL.cpp' + 'ECDatabaseUpdate.h' + 'ECDatabaseUpdate.cpp') md5sums=('0790d8314fa4aef9788e5020be832535' 'SKIP' 'SKIP' 'SKIP' + 'SKIP' + 'SKIP' + 'SKIP' + 'SKIP' 'SKIP') prepare() { cd ${srcdir}/zcp-${pkgver} + cp -f ${srcdir}/ECDBDef.h provider/libserver/ + cp -f ${srcdir}/ECDatabaseMySQL.cpp provider/libserver/ + cp -f ${srcdir}/ECDatabaseUpdate.h provider/libserver/ + cp -f ${srcdir}/ECDatabaseUpdate.cpp provider/libserver/ return 0 } |