1<?php 2require_once __DIR__ . '/config.inc'; 3require_once dirname(__DIR__, 4) . '/ext/pdo/tests/pdo_test.inc'; 4 5foreach ($env as $k => $v) { 6 define($k, $v); 7} 8 9class MySQLPDOTest extends PDOTest { 10 11 static function factory($classname = PDO::class, $mydsn = null, $myAttr = null, bool $useConnectMethod = false) { 12 $dsn = self::getDSN($mydsn); 13 $user = PDO_MYSQL_TEST_USER; 14 $pass = PDO_MYSQL_TEST_PASS; 15 $attr = PDO_MYSQL_TEST_ATTR; 16 17 if ($myAttr) { 18 $attr = $myAttr; 19 } else { 20 $attr = is_string($attr) && strlen($attr) ? unserialize($attr) : null; 21 } 22 23 if ($useConnectMethod) { 24 $db = $classname::connect($dsn, $user, $pass, $attr); 25 } else { 26 $db = new $classname($dsn, $user, $pass, $attr); 27 } 28 29 if (!$db) { 30 die("Could not create PDO object (DSN=$dsn, user=$user)\n"); 31 } 32 33 $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); 34 $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); 35 36 return $db; 37 } 38 39 static function factoryWithAttr($attr) { 40 return self::factory('PDO', null, $attr); 41 } 42 43 static function createTestTable($table, $db, $engine = null) { 44 if (!$engine) 45 $engine = PDO_MYSQL_TEST_ENGINE; 46 47 $db->exec("DROP TABLE IF EXISTS {$table}"); 48 $db->exec("CREATE TABLE {$table} (id INT, label CHAR(1), PRIMARY KEY(id)) ENGINE={$engine}"); 49 $db->exec("INSERT INTO {$table} (id, label) VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e'), (6, 'f')"); 50 } 51 52 static function getTableEngine() { 53 return PDO_MYSQL_TEST_ENGINE; 54 } 55 56 static function getDSN($new_options = null, $addition = '') { 57 if (!$new_options) 58 return PDO_MYSQL_TEST_DSN . $addition; 59 60 $old_options = array(); 61 $dsn = substr(PDO_MYSQL_TEST_DSN, 62 strpos(PDO_MYSQL_TEST_DSN, ':') + 1, 63 strlen(PDO_MYSQL_TEST_DSN)); 64 65 // no real parser - any exotic setting can fool us 66 $parts = explode(';', $dsn); 67 foreach ($parts as $k => $v) { 68 $tmp = explode('=', $v); 69 if (count($tmp) == 2) 70 $old_options[$tmp[0]] = $tmp[1]; 71 } 72 73 $options = $old_options; 74 foreach ($new_options as $k => $v) 75 $options[$k] = $v; 76 77 $dsn = 'mysql:'; 78 foreach ($options as $k => $v) 79 $dsn .= sprintf('%s=%s;', $k, $v); 80 81 if ($addition) 82 $dsn .= $addition; 83 else 84 $dsn = substr($dsn, 0, strlen($dsn) -1); 85 86 return $dsn; 87 } 88 89 static function getClientVersion($db) { 90 return self::extractVersion($db->getAttribute(PDO::ATTR_CLIENT_VERSION)); 91 } 92 93 static function getServerVersion($db) { 94 return self::extractVersion($db->getAttribute(PDO::ATTR_SERVER_VERSION)); 95 } 96 97 static function extractVersion($version_string) { 98 /* 99 TODO: 100 We're a bit in trouble: PDO_MYSQL returns version strings. 101 That's wrong according to the manual. According to the manual 102 integers should be returned. However, this code needs to work 103 with stinky PDO_MYSQL and hopefully better PDO_MYSQLND. 104 */ 105 106 // already an int value? 107 if (is_int($version_string)) 108 return $version_string; 109 110 // string but int value? 111 $tmp = (int)$version_string; 112 if (((string)$tmp) === $version_string) 113 return $tmp; 114 115 // stinky string which we need to parse 116 $parts = explode('.', $version_string); 117 if (count($parts) < 3) 118 return -1; 119 120 $version = (int)$parts[0] * 10000; 121 $version+= (int)$parts[1] * 100; 122 $version+= (int)$parts[2]; 123 124 return $version; 125 } 126 127 static function getTempDir() { 128 if (!empty($_ENV['TMP'])) 129 return realpath( $_ENV['TMP'] ); 130 if (!empty($_ENV['TMPDIR'])) 131 return realpath( $_ENV['TMPDIR'] ); 132 if (!empty($_ENV['TEMP'])) 133 return realpath( $_ENV['TEMP'] ); 134 135 $temp_file = tempnam(md5(uniqid(rand(), TRUE)), ''); 136 if ($temp_file) { 137 $temp_dir = realpath(dirname($temp_file)); 138 unlink($temp_file); 139 return $temp_dir; 140 } 141 return false; 142 } 143 144 static function detect_transactional_mysql_engine($db) { 145 foreach ($db->query("show variables like 'have%'") as $row) { 146 if (!empty($row) && $row[1] == 'YES' && ($row[0] == 'have_innodb' || $row[0] == 'have_bdb')) { 147 return str_replace("have_", "", $row[0]); 148 } 149 } 150 /* MySQL 5.6.1+ */ 151 foreach ($db->query("SHOW ENGINES") as $row) { 152 if (isset($row['engine']) && isset($row['support'])) { 153 if ('InnoDB' == $row['engine'] && ('YES' == $row['support'] || 'DEFAULT' == $row['support'])) 154 return 'innodb'; 155 } 156 } 157 return false; 158 } 159 160 static function isPDOMySQLnd() { 161 ob_start(); 162 phpinfo(); 163 $tmp = ob_get_contents(); 164 ob_end_clean(); 165 return (preg_match('/PDO Driver for MySQL.*enabled/', $tmp) && 166 preg_match('/Client API version.*mysqlnd/', $tmp)); 167 } 168 169 static function skip() { 170 try { 171 $db = self::factory(); 172 } catch (PDOException $e) { 173 die('skip could not connect'); 174 } 175 } 176 177 static function skipInfileNotAllowed() { 178 $db = self::factory(); 179 $stmt = $db->query("SHOW VARIABLES LIKE 'local_infile'"); 180 if (($row = $stmt->fetch(PDO::FETCH_ASSOC)) && ($row['value'] != 'ON')) 181 die("skip Server variable 'local_infile' seems not set to 'ON', found '". $row['value'] ."'"); 182 } 183 184 static function skipVersionThanLess ($expected_version) { 185 $nums = [ 186 (int) substr($expected_version, 0, 1), 187 (int) substr($expected_version, 1, 2), 188 (int) substr($expected_version, 3, 2), 189 ]; 190 $expected_version_str = implode('.', $nums); 191 192 $db = self::factory(); 193 $stmt = $db->query('SELECT VERSION() as _version'); 194 $row = $stmt->fetch(PDO::FETCH_ASSOC); 195 $matches = array(); 196 if (!preg_match('/^(\d+)\.(\d+)\.(\d+)/ismU', $row['_version'], $matches)) 197 die(sprintf("skip Cannot determine MySQL Server version\n")); 198 199 $version = $matches[1] * 10000 + $matches[2] * 100 + $matches[3]; 200 if ($version < $expected_version) 201 die(sprintf("skip Need MySQL Server %s+, found %d.%02d.%02d (%d)\n", 202 $expected_version_str, $matches[1], $matches[2], $matches[3], $version)); 203 } 204 205 static function skipNotMySQLnd(?string $message = null) { 206 $message = $message ?? 'skip only for mysqlnd'; 207 if (!self::isPDOMySQLnd()) die($message); 208 } 209 210 static function skipNotTransactionalEngine(?string $message = null) { 211 $db = self::factory(); 212 $message = $message ?? 'skip Transactional engine not found'; 213 if (false == self::detect_transactional_mysql_engine($db)) die($message); 214 } 215} 216?> 217