/** * Test convertEncoding() * */ function test_convertEncoding() { // Force client charset to latin1 so that the behaviour is similar to a real latin1 database // even if the database is encoded in utf8. At first we tried to drop the database an recreate // a latin1 encoded database, but PgSQL doesn't allow LATIN1 databases if the locale is set to // UTF-8. $GLOBALS['_MAX']['CONF']['databaseCharset'] = array('checkComplete' => true, 'clientCharset' => 'latin1'); // However MySQL versions < 4.1.2 didn't support charsets, so we don't need to do that // and assume that a database can store any 8bit data (which is in fact true) if ($this->oDbh->dbsyntax == 'mysql') { $aVersion = $this->oDbh->getServerVersion(); if (version_compare($aVersion['native'], '4.1.2', '<')) { $GLOBALS['_MAX']['CONF']['databaseCharset']['clientCharset'] = ''; } } // Set client charset OA_DB::setCharset($this->oDbh); // These tables are required for the encoding migration $aTables = array('acls', 'acls_channel', 'ad_zone_assoc', 'affiliates', 'affiliates_extra', 'agency', 'application_variable', 'banners', 'campaigns', 'channel', 'clients', 'preference', 'session', 'tracker_append', 'trackers', 'userlog', 'variables', 'zones'); // These tables are referenced by schema changes between 515 and 546, therefore need to be created $aOtherTables = array('preference_publisher', 'accounts', 'users', 'account_user_assoc'); //array('preference', 'data_raw_tracker_click', 'data_summary_zone_country_daily', 'data_summary_zone_country_forecast', 'data_summary_zone_country_monthly', 'data_summary_zone_domain_page_daily', 'data_summary_zone_domain_page_forecast', 'data_summary_zone_domain_page_monthly', 'data_summary_zone_site_keyword_daily', 'data_summary_zone_site_keyword_forecast', 'data_summary_zone_site_keyword_monthly', 'data_summary_zone_source_daily', 'data_summary_zone_source_forecast', 'data_summary_zone_source_monthly', 'preference_advertiser', 'preference_publisher'); $this->initDatabase(543, array_merge($aTables, $aOtherTables)); $this->aIds = TestEnv::loadData('encoding_schema_543', 'mdb2schema'); // MD5s verified manually setting the terminal encoding to the right encoding. $expected = array('latin1_utf8' => array(0 => array('campaignid' => $this->aIds['campaigns'][1], 'md5' => '1698982c38317c8c42ae4772bbee8f44'), 1 => array('campaignid' => $this->aIds['campaigns'][2], 'md5' => '317f56003783a2a9284306eb57fe8146'), 2 => array('campaignid' => $this->aIds['campaigns'][3], 'md5' => 'fa419947d425b10bd2485e090f4cae60'), 3 => array('campaignid' => $this->aIds['campaigns'][4], 'md5' => '32395feef462f13071c2a2fe5e44c7c0'), 4 => array('campaignid' => $this->aIds['campaigns'][5], 'md5' => '9932d540cb5b63f264b3f7391577fe93'), 5 => array('campaignid' => $this->aIds['campaigns'][6], 'md5' => 'c6ae927806e0a61f9cd269659a225435')), 'utf8_utf8' => array(0 => array('campaignid' => $this->aIds['campaigns'][1], 'md5' => '1698982c38317c8c42ae4772bbee8f44'), 1 => array('campaignid' => $this->aIds['campaigns'][2], 'md5' => '317f56003783a2a9284306eb57fe8146'), 2 => array('campaignid' => $this->aIds['campaigns'][3], 'md5' => '8c8755d8f519c0245717475757d043f7'), 3 => array('campaignid' => $this->aIds['campaigns'][4], 'md5' => '7269db488f9672cca26d93105a9a2559'), 4 => array('campaignid' => $this->aIds['campaigns'][5], 'md5' => '19397ed80befa5539761afed23c4c27a'), 5 => array('campaignid' => $this->aIds['campaigns'][6], 'md5' => 'a7d508c6c8a494c80e680033cecbc76d'))); $tblCampaigns = $this->oDbh->quoteIdentifier($this->getPrefix() . 'campaigns', true); // Check that the campaign names are correctly created: $query = "SELECT campaignid, campaignname FROM {$tblCampaigns}"; $result = $this->oDbh->queryAll($query); foreach (array_keys($result) as $k) { $result[$k]['md5'] = md5($result[$k]['campaignname']); unset($result[$k]['campaignname']); } $this->assertIdentical($result, $expected['latin1_utf8']); // Upgrade the dataset and ensure that the upgraded campaign names were upgraded correctly: $this->upgradeToVersion(544); // Fields requiring encoding changes should now be correct $query = "SELECT campaignid, campaignname FROM {$tblCampaigns}"; $result = $this->oDbh->queryAll($query); foreach (array_keys($result) as $k) { $result[$k]['md5'] = md5($result[$k]['campaignname']); unset($result[$k]['campaignname']); } $this->assertIdentical($result, $expected['utf8_utf8']); }
/** * A method to return a singleton database connection resource. * * Example usage: * $oDbh = OA_DB::singleton(); * * Warning: In order to work correctly, the singleton method must * be instantiated statically and by reference, as in the above * example. * * @static * @param string $dsn Optional database DSN details - connects to the * database defined by the configuration file otherwise. * See {@link OA_DB::getDsn()} for format. * * @param array $aDriverOptions An optional array of driver options. Currently * supported options are: * - For MySQL: * ['ssl'] = false|true Perform connection over SSL? * ['ca'] = Name of CA file, if "ssl" true * ['capath'] = Path to CA file above, is "ssl" true * ['compress'] = false|true Use client compression? * * @return MDB2_Driver_Common An MDB2 connection resource, or PEAR_Error * on failure to connect. */ static function singleton($dsn = null, $aDriverOptions = array()) { $aConf = $GLOBALS['_MAX']['CONF']; // Get the driver options, if not set if (!is_array($aDriverOptions) || is_null($dsn) && empty($aDriverOptions)) { $aDriverOptions = OA_DB::getDsnOptions(); } // Get the DSN, if not set $dsn = is_null($dsn) ? OA_DB::getDsn() : $dsn; // Check that the parameter is a string, not an array if (is_array($dsn)) { return Max::raiseError('Bad argument: DSN should be a string', MAX_ERROR_INVALIDARGS); } // A hack to allow for installation on pgsql // If the configuration hasn't been defined prevent // loading mysql MDB2 driver. if (strpos($dsn, '//:@') !== false) { // Return a silent error return new PEAR_Error('Bad argument: Empty DSN'); } // Get the database type in use from the DNS, not from the // configuration file $aDSN = MDB2::parseDSN($dsn); $databaseType = $aDSN['phptype']; // Is this a MySQL database connection that should happen via SSL? if (strcasecmp($databaseType, 'mysql') === 0 && @$aDriverOptions['ssl']) { // Modify the DSN string to include the required CA and CAPATH options if (!empty($aDriverOptions['ca']) && !empty($aDriverOptions['capath'])) { $dsn .= "?ca={$aDriverOptions['ca']}&capth={$aDriverOptions['capath']}"; } } // Create an MD5 checksum of the DSN $dsnMd5 = md5($dsn); // Does this database connection already exist? if (isset($GLOBALS['_OA']['CONNECTIONS'])) { $aConnections = array_keys($GLOBALS['_OA']['CONNECTIONS']); } else { $aConnections = array(); } if (!(count($aConnections) > 0) || !in_array($dsnMd5, $aConnections)) { // Prepare options for a new database connection $aOptions = array(); // Sequence column name $aOptions['seqcol_name'] = 'id'; // Set the index name format $aOptions['idxname_format'] = '%s'; // Use 4 decimal places in DECIMAL nativetypes $aOptions['decimal_places'] = 4; // Set the portability options $aOptions['portability'] = OA_DB_MDB2_DEFAULT_OPTIONS; // Set the default table type for MySQL, if appropriate if (strcasecmp($databaseType, 'mysql') === 0) { if (!empty($aConf['table']['type'])) { $aOptions['default_table_type'] = $aConf['table']['type']; // Enable transaction support when using InnoDB tables if (strcasecmp($aOptions['default_table_type'], 'innodb') === 0) { // Enable transaction support $aOptions['use_transactions'] = true; } } } elseif (strcasecmp($databaseType, 'pgsql') === 0) { $aOptions['quote_identifier'] = true; } // Add default charset - custom OpenX if (defined('OA_DB_MDB2_DEFAULT_CHARSET')) { $aOptions['default_charset'] = OA_DB_MDB2_DEFAULT_CHARSET; } else { $aOptions['default_charset'] = 'utf8'; } // this will log select queries to a var/sql.log // currently used for analysis purposes if (isset($aConf['debug']['logSQL']) && $aConf['debug']['logSQL']) { $aOptions['log_statements'] = explode('|', $aConf['debug']['logSQL']); $aOptions['debug'] = true; $aOptions['debug_handler'] = 'logSQL'; } $aOptions += OA_DB::getDatatypeMapOptions(); // Is this a MySQL database connection? if (strcasecmp($databaseType, 'mysql') === 0) { // Should this connection happen over SSL? if (@$aDriverOptions['ssl']) { $aOptions['ssl'] = true; } } // Create the new database connection OA::disableErrorHandling(); $oDbh = MDB2::singleton($dsn, $aOptions); OA::enableErrorHandling(); if (PEAR::isError($oDbh)) { return $oDbh; } // Is this a MySQL database connection? if (strcasecmp($databaseType, 'mysql') === 0) { $client_flags = 0; // Should this connection happen over SSL? if (@$aDriverOptions['ssl']) { $client_flags = $client_flags | MYSQL_CLIENT_SSL; } // Should this connection use compression? if (@$aDriverOptions['compress']) { $client_flags = $client_flags | MYSQL_CLIENT_COMPRESS; } // Are there any MySQL connection flags to set? if ($client_flags != 0) { $oDbh->dsn['client_flags'] = $client_flags; } } OA::disableErrorHandling(); $success = $oDbh->connect(); OA::enableErrorHandling(); if (PEAR::isError($success)) { return $success; } // Set charset if needed $success = OA_DB::setCharset($oDbh); if (PEAR::isError($success)) { return $success; } // Set schema if needed $success = OA_DB::setSchema($oDbh); if (PEAR::isError($success)) { return $success; } // Set the fetchmode to be use used $oDbh->setFetchMode(MDB2_FETCHMODE_ASSOC); // Load modules that are likely to be needed $oDbh->loadModule('Extended'); $oDbh->loadModule('Datatype'); $oDbh->loadModule('Manager'); // Store the database connection $GLOBALS['_OA']['CONNECTIONS'][$dsnMd5] = $oDbh; // Set MySQL 4 compatibility if needed if (strcasecmp($databaseType, 'mysql') === 0 && !empty($aConf['database']['mysql4_compatibility'])) { $oDbh->exec("SET SESSION sql_mode='MYSQL40'"); } } return $GLOBALS['_OA']['CONNECTIONS'][$dsnMd5]; }