dbMySQLHost; if ($host[0] === "/") { // host is a Unix socket $socket = $host; $host = ""; } elseif(substr($host, 0, 9) === "\\\\.\\pipe\\") { // host is a Windows named piple $socket = substr($host, 10); $host = ""; } $user = Arsse::$conf->dbMySQLUser ?? ""; $pass = Arsse::$conf->dbMySQLPass ?? ""; $port = Arsse::$conf->dbMySQLPost ?? 3306; $db = Arsse::$conf->dbMySQLDb ?? "arsse"; // make the connection $this->makeConnection($user, $pass, $db, $host, $port, $socket ?? ""); // set session variables foreach (static::makeSetupQueries() as $q) { $this->exec($q); } // get the maximum packet size; parameter strings larger than this size need to be chunked $this->packetSize = $this->query("select variable_value from performance_schema.session_variables where variable_name = 'max_allowed_packet'")->getValue(); } public static function makeSetupQueries(): array { return [ "SET sql_mode = '".self::SQL_MODE."'", "SET time_zone = '+00:00'", "SET lock_wait_timeout = 1", ]; } /** @codeCoverageIgnore */ public static function create(): \JKingWeb\Arsse\Db\Driver { if (self::requirementsMet()) { return new self; } elseif (PDODriver::requirementsMet()) { return new PDODriver; } else { throw new Exception("extMissing", self::driverName()); } } public static function schemaID(): string { return "MySQL"; } public function charsetAcceptable(): bool { return true; } public function schemaVersion(): int { if ($this->query("SELECT count(*) from information_schema.tables where table_name = 'arsse_meta'")->getValue()) { return (int) $this->query("SELECT value from arsse_meta where `key` = 'schema_version'")->getValue(); } else { return 0; } } public function sqlToken(string $token): string { switch (strtolower($token)) { case "nocase": return '"utf8mb4_unicode_ci"'; default: return $token; } } public function savepointCreate(bool $lock = false): int { if (!$this->transStart && !$lock) { $this->exec("BEGIN"); $this->transStart = parent::savepointCreate($lock); return $this->transStart; } else { return parent::savepointCreate($lock); } } public function savepointRelease(int $index = null): bool { $index = $index ?? $this->transDepth; $out = parent::savepointRelease($index); if ($index == $this->transStart) { $this->exec("COMMIT"); $this->transStart = 0; } return $out; } public function savepointUndo(int $index = null): bool { $index = $index ?? $this->transDepth; $out = parent::savepointUndo($index); if ($index == $this->transStart) { $this->exec("ROLLBACK"); $this->transStart = 0; } return $out; } protected function lock(): bool { $tables = $this->query("SELECT table_name as name from information_schema.tables where table_schema = database() and table_name like 'arsse_%'")->getAll(); if ($tables) { $tables = array_column($tables, "name"); $tables = array_map(function($table) { $table = str_replace('"', '""', $table); return "\"$table\" write"; }, $tables); $tables = implode(", ", $tables); try { $this->exec("SET lock_wait_timeout = 1; LOCK TABLES $tables"); } finally { $this->exec("SET lock_wait_timeout = 60"); } } return true; } protected function unlock(bool $rollback = false): bool { $this->exec("UNLOCK TABLES"); return true; } public function __destruct() { if (isset($this->db)) { $this->db->close(); unset($this->db); } } public static function driverName(): string { return Arsse::$lang->msg("Driver.Db.MySQL.Name"); } public static function requirementsMet(): bool { return false; } protected function makeConnection(string $db, string $user, string $password, string $host, int $port, string $socket) { try { $this->db = new \mysqli($host, $user, $password, $db, $port, $socket); if ($this->db->connect_errno) { echo $this->db->connect_errno.": ".$this->db->connect_error; } $this->db->set_character_set("utf8mb4"); } catch (\Exception $e) { throw $e; } } public function exec(string $query): bool { $this->dispatch($query); return true; } protected function dispatch(string $query) { $r = $this->db->query($query); if ($this->db->sqlstate !== "00000") { if ($this->db->sqlstate === "HY000") { list($excClass, $excMsg, $excData) = $this->buildEngineException($this->db->errno, $this->db->error); } else { list($excClass, $excMsg, $excData) = $this->buildStandardException($this->db->sqlstate, $this->db->error); } throw new $excClass($excMsg, $excData); } return $r; } public function query(string $query): \JKingWeb\Arsse\Db\Result { $r = $this->dispatch($query); $rows = (int) $this->db->affected_rows; $id = (int) $this->db->insert_id; if ($r === true) { return new \JKingWeb\Arsse\Db\ResultEmpty($rows, $id); } else { return new ResultE($r, [$rows, $id]); } } public function prepareArray(string $query, array $paramTypes): \JKingWeb\Arsse\Db\Statement { return new Statement($this->db, $query, $paramTypes, $this->packetSize); } }