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