/*
* 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 .
*
*/
#include
#include
#include
#include "ECDatabaseMySQL.h"
#include "mysqld_error.h"
#include
#include
#include "ECDBDef.h"
#include "ECUserManagement.h"
#include
#include
#include
#include
#include
#include
#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(server_args),
const_cast(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(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 listTables;
list listErrorTables;
list::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;
}