2018-12-20 23:06:28 +00:00
< ? php
/** @ license MIT
* Copyright 2017 J . King , Dustin Wilson et al .
* See LICENSE and AUTHORS files for details */
declare ( strict_types = 1 );
namespace JKingWeb\Arsse\Db\MySQL ;
use JKingWeb\Arsse\Arsse ;
use JKingWeb\Arsse\Conf ;
use JKingWeb\Arsse\Db\Exception ;
use JKingWeb\Arsse\Db\ExceptionInput ;
use JKingWeb\Arsse\Db\ExceptionTimeout ;
class Driver extends \JKingWeb\Arsse\Db\AbstractDriver {
2019-01-11 00:01:32 +00:00
use ExceptionBuilder ;
2018-12-21 01:50:56 +00:00
const SQL_MODE = " ANSI_QUOTES,HIGH_NOT_PRECEDENCE,NO_BACKSLASH_ESCAPES,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT,STRICT_ALL_TABLES " ;
2018-12-20 23:06:28 +00:00
const TRANSACTIONAL_LOCKS = false ;
2019-01-11 00:01:32 +00:00
/** @var \mysql */
2018-12-20 23:06:28 +00:00
protected $db ;
protected $transStart = 0 ;
2019-01-11 00:01:32 +00:00
protected $packetSize = 4194304 ;
2018-12-20 23:06:28 +00:00
public function __construct () {
// check to make sure required extension is loaded
if ( ! static :: requirementsMet ()) {
throw new Exception ( " extMissing " , static :: driverName ()); // @codeCoverageIgnore
}
$host = Arsse :: $conf -> dbMySQLHost ;
if ( $host [ 0 ] == " / " ) {
2019-01-11 00:01:32 +00:00
// host is a Unix socket
2018-12-20 23:06:28 +00:00
$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 " ;
2019-01-11 00:01:32 +00:00
// make the connection
2018-12-20 23:06:28 +00:00
$this -> makeConnection ( $user , $pass , $db , $host , $port , $socket ? ? " " );
2019-01-11 00:01:32 +00:00
// 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 " ,
];
2018-12-20 23:06:28 +00:00
}
/** @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 " :
2018-12-21 01:50:56 +00:00
return '"utf8mb4_unicode_ci"' ;
2018-12-20 23:06:28 +00:00
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 {
2018-12-21 01:50:56 +00:00
$this -> exec ( " SET lock_wait_timeout = 60 " );
2018-12-20 23:06:28 +00:00
}
}
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 ) {
2019-01-11 00:01:32 +00:00
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 ;
}
2018-12-20 23:06:28 +00:00
}
2019-01-11 00:01:32 +00:00
public function exec ( string $query ) : bool {
$this -> dispatch ( $query );
return true ;
2018-12-20 23:06:28 +00:00
}
2019-01-11 00:01:32 +00:00
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 ;
2018-12-20 23:06:28 +00:00
}
public function query ( string $query ) : \JKingWeb\Arsse\Db\Result {
2019-01-11 00:01:32 +00:00
$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 ]);
}
2018-12-20 23:06:28 +00:00
}
public function prepareArray ( string $query , array $paramTypes ) : \JKingWeb\Arsse\Db\Statement {
2019-01-11 00:01:32 +00:00
return new Statement ( $this -> db , $query , $paramTypes , $this -> packetSize );
2018-12-20 23:06:28 +00:00
}
}