/** * @test * @group DbHelper * @group reusesDbConnection * @group reuseDb */ public function reusesDbConnection() { $config = Hashmark::getConfig('DbHelper'); $link = new mysqli($config['profile']['unittest']['params']['host'], $config['profile']['unittest']['params']['username'], $config['profile']['unittest']['params']['password'], $config['profile']['unittest']['params']['dbname'], $config['profile']['unittest']['params']['port']); $db = Hashmark::getModule('DbHelper')->reuseDb($link, 'Mysqli'); $this->assertEquals('mysqli', get_class($db->getConnection())); }
/** * Resources needed for most tests. * * @return void */ protected function setUp() { parent::setUp(); $this->_partition = Hashmark::getModule('Partition', '', $this->_db); $this->_core = Hashmark::getModule('Core', '', $this->_db); $this->_mergeTablePrefix = Hashmark::getConfig('Partition', '', 'mergetable_prefix'); }
/** * Called by Hashmark::getModule() to inject dependencies. * * @param mixed $db Connection object/resource. * @param string $dbName Database selection, unquoted. [optional] * @param Hashmark_Partition $partition Initialized instance. * @return boolean False if module could not be initialized and is unusable. * Hashmark::getModule() will also then return false. */ public function initModule($db, $partition = '') { parent::initModule($db); $this->_partition = $partition; $dbHelperConfig = Hashmark::getConfig('DbHelper'); $this->_divPrecisionIncr = $dbHelperConfig['div_precision_increment']; return true; }
/** * @test * @group Cron * @group collectsGarbageMergeTables * @group getAllMergeTables */ public function collectsGarbageMergeTables() { $partition = Hashmark::getModule('Partition', '', $this->_db); $mergeTablePrefix = Hashmark::getConfig('Partition', '', 'mergetable_prefix'); // Drop all merge tables to clear the slate. $priorMergeTables = $partition->getTablesLike($mergeTablePrefix . '%'); if ($priorMergeTables) { $partition->dropTable($priorMergeTables); } $scalar = array(); $scalar['name'] = self::randomString(); $scalar['type'] = 'decimal'; $scalarId = Hashmark::getModule('Core', '', $this->_db)->createScalar($scalar); $regTableName = 'test_samples_' . self::randomString(); $partition->createTable($scalarId, $regTableName, $scalar['type']); $now = time(); $start = '2008-04-01 01:45:59'; $mergeTableNames = array(); // Create several merge tables from the same single normal table. // Space them 1 day apart. // Vary $end to make the merge table names unique. for ($t = 0; $t < 5; $t++) { $end = "2009-06-1{$t} 01:45:59"; $comment = gmdate(HASHMARK_DATETIME_FORMAT, $now - $t * 86400); $mergeTableNames[$t] = $mergeTablePrefix . "{$scalarId}_20080401_2009061{$t}"; $actualTable = $partition->createMergeTable($scalarId, $start, $end, array($regTableName), $comment); $this->assertEquals($mergeTableNames[$t], $actualTable); } $maxDays = 3; $maxCount = 2; ob_start(); require HASHMARK_ROOT_DIR . '/Cron/gcMergeTables.php'; ob_end_clean(); for ($t = 0; $t < 5; $t++) { if ($t < 2) { $this->assertTrue($partition->tableExists($mergeTableNames[$t]), "Expected {$mergeTableNames[$t]} to exist"); } else { $this->assertFalse($partition->tableExists($mergeTableNames[$t]), "Expected {$mergeTableNames[$t]} to be missing"); } } }
/** * Return minimum based on bccomp(). * * @param Array $values * @return string Minimum value. * @throws Exception if $values is not a populated Array. */ public static function min($values) { if (!is_array($values) || empty($values)) { throw new Exception('min() requires a populated Array.', HASHMARK_EXCEPTION_VALIDATION); } bcscale(Hashmark::getConfig('DbHelper', '', 'decimal_right_width')); usort($values, 'bccomp'); return $values[0]; }
* @package Hashmark * @subpackage Cron * @version $Id$ */ /** * For getModule() and cron constants. */ require_once dirname(__FILE__) . '/../Hashmark.php'; // Max. table age in days. if (!isset($maxDays)) { $maxDays = Hashmark::getConfig('Cron', '', 'merge_gc_max_days'); } // Max. table count. Non-expired tables will be sorted // by age descending and then pruned. if (!isset($maxCount)) { $maxCount = Hashmark::getConfig('Cron', '', 'merge_gc_max_count'); } $db = Hashmark::getModule('DbHelper')->openDb('cron'); $partition = Hashmark::getModule('Partition', '', $db); $garbageTables = array(); $keepTables = array(); $now = time(); $all = $partition->getAllMergeTables(); foreach ($all as $table) { $unixTime = strtotime($table['TABLE_COMMENT'] . ' UTC'); // Ex. merge tables who no longer match an updated partition schema. if (!$unixTime) { $garbageTables[] = $table['TABLE_NAME']; continue; } if ($unixTime + $maxDays * 86400 < $now) {
/** * Testable logic for assertDecimalEquals(). * * @param string $expected * @param string $actual * @return boolean True if equal. */ public static function checkDecimalEquals($expected, $actual) { if (!is_string($expected) || !is_string($actual)) { return false; } bcscale(Hashmark::getConfig('DbHelper', '', 'decimal_right_width')); return 0 === bccomp($expected, $actual); }
* - @name macros will not be escaped nor quoted, ex. SQL functions. * - ~name table name macros will be backtick-quoted. * - To set the destination columns for the INSERT INTO ... SELECT * that populates a temp. table with macro named ~exampleTemp~, * define macro @exampleTempCols, ex. w/ value: `x`, `y` * - ROUND() used to avoid truncation warnings. * * @filesource * @copyright Copyright (c) 2008-2011 David Smith * @license http://www.opensource.org/licenses/mit-license.php MIT License * @package Hashmark * @subpackage Sql * @version $Id$ */ $decimalTotalWidth = Hashmark::getConfig('DbHelper', '', 'decimal_total_width'); $decimalRightWidth = Hashmark::getConfig('DbHelper', '', 'decimal_right_width'); $sql = array(); $sql['values'] = 'SELECT `end` AS `x`, ' . '`value` AS `y` ' . 'FROM ~samples ' . 'WHERE `end` >= ? ' . 'AND `end` <= ? '; /** * - Self-join allows us to pull the most recent row from inside each * interval to reprsent the whole. * - Duplicate the start/end conditions in the self-join * in order to narrow `s2` scan. */ $sql['valuesAtInterval'] = 'SELECT `s1`.`end` AS `x`, ' . '`s1`.`value` AS `y` ' . 'FROM ~samples AS `s1` ' . 'LEFT JOIN ~samples AS `s2` ' . 'ON DATE_FORMAT(`s1`.`end`, ?) = DATE_FORMAT(`s2`.`end`, ?) ' . 'AND `s1`.`end` < `s2`.`end` ' . 'AND `s2`.`end` >= ? ' . 'AND `s2`.`end` <= ? ' . 'WHERE `s1`.`end` >= ? ' . 'AND `s1`.`end` <= ? ' . 'AND `s2`.`end` IS NULL '; $sql['valuesAgg'] = 'SELECT ROUND(@aggFunc(@distinct`value`), ' . $decimalRightWidth . ') AS `y` ' . 'FROM ~samples ' . 'WHERE `end` >= ? ' . 'AND `end` <= ? '; $sql['valuesAggAtInterval'] = 'SELECT DATE_FORMAT(`end`, ?) AS `x`, ' . 'ROUND(@aggFunc(@distinct`value`), ' . $decimalRightWidth . ') AS `y` ' . 'FROM ~samples ' . 'WHERE `end` >= ? ' . 'AND `end` <= ? ' . 'GROUP BY `x` '; $sql['valuesNestedAggAtInterval'] = 'SELECT ROUND(@aggFunc(@distinct`y2`), ' . $decimalRightWidth . ') AS `y2` ' . 'FROM ~valuesAggAtInterval '; $sql['valuesAggAtRecurrence'] = 'SELECT @recurFunc(`end`) AS `x`, ' . 'ROUND(@aggFunc(@distinct`value`), ' . $decimalRightWidth . ') AS `y` ' . 'FROM ~samples ' . 'WHERE `end` >= ? ' . 'AND `end` <= ? ' . 'GROUP BY @recurFunc(`end`) '; /** * - Duplicate the start/end conditions in the self-join
*/ $modList = array('BcMath' => '', 'Cache' => '', 'Client' => '', 'Core' => '', 'DbHelper' => '', 'Partition' => '', 'Agent' => 'YahooWeather', 'Test' => 'FakeModuleType'); foreach ($modList as $baseName => $typeName) { // Cache modules don't use the DB argument, // but it should have no effect. $inst = Hashmark::getModule($baseName, $typeName, $mockDb); if ($typeName) { $className = "Hashmark_{$baseName}_{$typeName}"; } else { $className = "Hashmark_{$baseName}"; } $testDetail = "{$className} module.\n"; if ($inst instanceof $className) { echo "pass: Loaded {$testDetail}"; } else { echo "====> FAIL: Could not load {$testDetail}"; } } /** * * Misc. configuration value checks. * */ $mockScalarId = 1234; $partitionTableName = Hashmark::getModule('Partition', '', $mockDb)->getIntervalTableName($mockScalarId); $testDetail = "partition name with '" . Hashmark::getConfig('Partition', '', 'interval') . "' setting in Config/Partition.php.\n"; if ($partitionTableName) { echo "pass: Built {$partitionTableName} {$testDetail}"; } else { echo "====> FAIL: Could not build {$testDetail}"; }
/** * Increment a scalar identified by name. * * @param string $name * @param string $amount * @param boolean $newSample If true, a new sample partition row is inserted * `scalars` is updated. * @return boolean True on success. * @throws Exception On query error or non-string $scalarName. */ public function incr($scalarName, $amount = '1', $newSample = false) { if (!is_string($scalarName)) { throw new Exception('Cannot look up scalar with a non-string name.', HASHMARK_EXCEPTION_VALIDATION); } if (!preg_match('/[0-9.-]+/', $amount)) { throw new Exception('Cannot increment/decrement a scalar an invalid amount.', HASHMARK_EXCEPTION_VALIDATION); } if ($this->_createScalarIfNotExists) { $core = $this->getModule('Core'); $scalarId = $core->getScalarIdByName($scalarName); if (!$scalarId) { $fields = array('name' => $scalarName, 'type' => 'decimal', 'value' => $amount, 'description' => 'Auto-created by client'); $scalarId = $core->createScalar($fields); if (!$scalarId) { throw new Exception("Scalar '{$scalarName}' was not auto-created", HASHMARK_EXCEPTION_SQL); } if ($newSample) { $sql = 'INSERT INTO ~samples ' . '(`value`, `end`) ' . "VALUES ({$amount}, UTC_TIMESTAMP())"; $partition = $this->getModule('Partition'); $partition->queryCurrent($scalarId, $sql); } return true; } } $dbHelperConfig = Hashmark::getConfig('DbHelper'); $sum = 'CONVERT(`value`, DECIMAL' . "({$dbHelperConfig['decimal_total_width']},{$dbHelperConfig['decimal_right_width']})) + " . $this->escape($amount); if ($newSample) { $sql = "UPDATE {$this->_dbName}`scalars` " . "SET `value` = {$sum}, " . '`last_inline_change` = UTC_TIMESTAMP() ' . 'WHERE `name` = ? ' . 'AND `type` = "decimal"'; $stmt = $this->_db->query($sql, array($scalarName)); if (!$stmt->rowCount()) { return false; } unset($stmt); $currentScalarValue = "SELECT `value` FROM {$this->_dbName}`scalars` WHERE `name` = ? LIMIT 1"; $sql = 'INSERT INTO ~samples ' . '(`value`, `end`) ' . "VALUES (({$currentScalarValue}), UTC_TIMESTAMP())"; $scalarId = $this->getModule('Core')->getScalarIdByName($scalarName); $partition = $this->getModule('Partition'); $stmt = $partition->queryCurrent($scalarId, $sql, array($scalarName)); } else { $sql = "UPDATE {$this->_dbName}`scalars` " . "SET `value` = {$sum}, " . '`last_inline_change` = UTC_TIMESTAMP() ' . 'WHERE `name` = ? ' . 'AND `type` = "decimal"'; $stmt = $this->_db->query($sql, array($scalarName)); } return 1 == $stmt->rowCount(); }