From 9caf7d87f3e1e3349ffae4c3e549a259c413e90d Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 19 Jul 2024 00:59:55 -0700 Subject: [PATCH 1/4] PHP DB code: clean up the logic, and allow for > 1 readonly replica --- html/inc/boinc_db.inc | 170 ++++++++++++++++++------------------------ html/inc/db.inc | 15 +--- html/inc/db_conn.inc | 133 ++++++++++----------------------- 3 files changed, 113 insertions(+), 205 deletions(-) diff --git a/html/inc/boinc_db.inc b/html/inc/boinc_db.inc index 63ca43d5b44..5e77cd1049d 100644 --- a/html/inc/boinc_db.inc +++ b/html/inc/boinc_db.inc @@ -27,119 +27,97 @@ incs(); class BoincDb extends DbConn { static $instance; - // connect to the database (possibly to a read-only replica) - // NOTE: choice of replica can be made only at the page level. - // If there's a page that's guaranteed to do only reads, put - // BoincDb::get(true); - // at the top of it. + // A project can have one or more databases: + // DB 0: + // the main DB; read/write + // identified in config file by db_host, db_name, db_user, db_passwd + // db_host defaults to localhost + // DB 1: + // read-only replica; identified by + // replica_db_host/name/user/passwd (must include all) + // DB 2: + // read-only replica; identified by + // replica2_db_host/name/user/passwd (must include all) + // ... and potentially more + + // connect to DB $dbnum (0, 1, ...) + // If the requested DB doesn't exist or connection fails, connect to DB 0. + // Set self::$instance; no return value // - // Specify a $fallback_mode that is used when $readonly is true: - // 0: default, use db_user if no replica_db_user is specified, - // first try replica_db_host (if specified) then db_host - // 1: only use replica_db_user, first try replica_db_host then db_host - // 2: only use replica_db_user, only try replica_db_host - // can be set projectwide using - // - static function get_aux($readonly, $fallback_mode = 0) { - $config = get_config(); - $user = parse_config($config, ''); - $passwd = parse_config($config, ''); - $host = parse_config($config, ''); - $replica_host = parse_config($config, ''); - $name = parse_config($config, ''); - $fm = parse_config($config, ''); - if ($fm) { - // override parameter with config.xml setting - $fallback_mode = $fm; - } - if ($host == null) { - $host = "localhost"; - } + static function get_aux($dbnum) { $instance = new DbConn(); - if ($readonly) { - if (($fallback_mode > 0) && (!$replica_host)) { - error_log("BoincDb::get_aux(): required for \$fallback_mode > 0 (giving up)"); - $instance = null; - self::$instance = $instance; - return $instance; - } - $u = parse_config($config, ''); - $p = parse_config($config, ''); - $n = parse_config($config, ''); - if (($fallback_mode > 0) && (!$u || !$p || !$n)) { - error_log("BoincDb::get_aux(): required for \$fallback_mode > 0 (giving up)"); - $instance = null; - self::$instance = $instance; - return $instance; - } else { - // use replica user if given or use normal user for $fallback_mode == 0 - if ($u) $user = $u; - if ($p) $passwd = $p; - if ($n) $name = $n; - } - // skip this block if no $replica_host is specified for $fallback_mode == 0 - if ($replica_host) { - $retval = $instance->init_conn( - $user, $passwd, $replica_host, $name, true - ); + self::$instance = null; + $config = get_config(); + if ($dbnum) { + $r = $dbnum==1?'':strval($dbnum); + $host = parse_config($config, sprintf('', $r)); + $name = parse_config($config, sprintf('', $r)); + $user = parse_config($config, sprintf('', $r)); + $passwd = parse_config($config, sprintf('', $r)); + if ($host && $name && $user && $passwd) { + $retval = $instance->init_conn($user, $passwd, $host, $name); if ($retval) { - // needed for places where we do direct queries - if (!$instance->do_query("use $name")) { - error_log("BoincDb::get_aux(): Couldn't select database $name on $replica_host (giving up)"); - $instance = null; + if ($instance->do_query("use $name")) { + //error_log("BoincDb::get_aux(): connected to replica DB $dbnum"); + self::$instance = $instance; + return; } - self::$instance = $instance; - return $instance; - } elseif ($fallback_mode == 2) { - // no fallback to master in this case - error_log("BoincDb::get_aux(): Couldn't connect to $user@$replica_host (giving up)"); - $instance = null; - self::$instance = $instance; - return $instance; - } else { - error_log("BoincDb::get_aux(): Couldn't connect to $user@$replica_host (trying $user@$host next)"); } } + // if can't connect to replica, fall through and try DB 0 } - $retval = $instance->init_conn($user, $passwd, $host, $name, false); - if (!$retval) { - $instance = null; - error_log("BoincDb::get_aux(): Couldn't connect to $user@$host (giving up)"); - } else { - // needed for places where we do direct queries - if (!$instance->do_query("use $name")) { - error_log("BoincDb::get_aux(): Couldn't select database $name on $host (giving up)"); - $instance = null; + $host = parse_config($config, ''); + $user = parse_config($config, ''); + $name = parse_config($config, ''); + $passwd = parse_config($config, ''); + // OK if host is null; it will use localhost + if (!$name || !$user || !$passwd) { + error_log("BoincDb::get_aux(): must specify DB name, user, passwd"); + return; + } + $retval = $instance->init_conn($user, $passwd, $host, $name); + if ($retval) { + if ($instance->do_query("use $name")) { + //error_log("BoincDb::get_aux(): connected to DB $dbnum"); + self::$instance = $instance; + return; } } - self::$instance = $instance; - return $instance; + error_log("BoincDb::get_aux(): Couldn't connect to DB $dbnum"); } - // same, but + // connect to DB $dbnum, but first: // 1) check for a cached connection // 2) check whether the "stop_web" trigger file is present // - static function get($readonly = false, $fallback_mode = 0) { + // If there's a page that's guaranteed to do only reads, put + // BoincDb::get(true); + // at the top of it. + // + // Note: true == 1. + // You can also 2, 3... to select other replicas + // + static function get($dbnum = 0) { global $generating_xml; - if (!isset(self::$instance)) { - if (web_stopped()) { - if ($generating_xml) { - xml_error(-183, "project down for maintenance"); - } else { - show_project_down(); - } - } - self::get_aux($readonly, $fallback_mode); - if (!self::$instance) { - if ($generating_xml) { - xml_error(-138, "Can't connect to database"); - } else { - error_page("Can't connect to database"); - } + if (isset(self::$instance)) { + return self::$instance; + } + if (web_stopped()) { + if ($generating_xml) { + xml_error(-183, "project down for maintenance"); + } else { + show_project_down(); } } - return self::$instance; + self::get_aux($dbnum); + if (self::$instance) { + return self::$instance; + } + if ($generating_xml) { + xml_error(-138, "Can't connect to database"); + } else { + error_page("Can't connect to database"); + } } static function escape_string($string) { diff --git a/html/inc/db.inc b/html/inc/db.inc index 0238e80475d..1c51d3b5b03 100644 --- a/html/inc/db.inc +++ b/html/inc/db.inc @@ -24,20 +24,7 @@ require_once("../inc/util_basic.inc"); // DEPRECATED; use boinc_db.inc instead. // TODO: replace calls to these functions -// use mysqli if available, -// but let projects not use it if they want -// (put in config.xml) -// -if (parse_bool(get_config(), "no_mysqli")) { - define("MYSQLI", false); -} else { - if (class_exists("mysqli")) { - define("MYSQLI", true); - $mysqli = null; - } else { - define("MYSQLI", false); - } -} +define("MYSQLI", true); if (MYSQLI) { function _mysql_connect($host, $user, $pass, $dbname) { diff --git a/html/inc/db_conn.inc b/html/inc/db_conn.inc index 7eb0fb70d06..11d2573b063 100644 --- a/html/inc/db_conn.inc +++ b/html/inc/db_conn.inc @@ -24,37 +24,33 @@ require_once("../inc/db.inc"); class DbConn { var $db_conn; var $db_name; - var $readonly; - function init_conn($user, $passwd, $host, $name, $readonly) { - if (MYSQLI) { - $x = explode(":", $host); - if (sizeof($x)>1) { - $host = $x[0]; - $port = $x[1]; - } else { - $port = null; - } - //if (version_compare(PHP_VERSION, '5.3.0') < 0) { - if (1) { // don't use persistent connections for now - $this->db_conn = @new mysqli($host, $user, $passwd, $name, $port); - } else { - $this->db_conn = @new mysqli("p:".$host, $user, $passwd, $name, $port); - } - // mysqli returns an object even if the connection is not established - if (mysqli_connect_error()) { - return false; - } - global $mysqli; - $mysqli = $this->db_conn; + function init_conn($user, $passwd, $host, $name) { + $x = explode(":", $host); + if (sizeof($x)>1) { + $host = $x[0]; + $port = $x[1]; + } else { + $port = null; + } + if (1) { // don't use persistent connections for now + $this->db_conn = @new mysqli( + $host, $user, $passwd, $name, $port + ); } else { - $this->db_conn = @mysql_pconnect($host, $user, $passwd); + $this->db_conn = @new mysqli( + 'p:'.$host, $user, $passwd, $name, $port + ); + } + if (mysqli_connect_error()) { + return false; } + global $mysqli; + $mysqli = $this->db_conn; if (!$this->db_conn) { return false; } $this->db_name = $name; - $this->readonly = $readonly; return true; } @@ -65,11 +61,7 @@ class DbConn { global $generating_xml; $q = str_replace('DBNAME', $this->db_name, $q); //echo "query: $q
\n"; - if (MYSQLI) { - $ret = $this->db_conn->query($q); - } else { - $ret = mysql_query($q, $this->db_conn); - } + $ret = $this->db_conn->query($q); if (!$ret) { if (!$generating_xml) { echo "Database Error
\n"; @@ -85,11 +77,7 @@ class DbConn { // # rows affected by last query // function affected_rows() { - if (MYSQLI) { - return $this->db_conn->affected_rows; - } else { - return mysql_affected_rows($this->db_conn); - } + return $this->db_conn->affected_rows; } function get_list($table1, $table2, $joinfield1, $joinfield2, $classname, $fields, $where_clause, $order_clause, $limit) { @@ -97,17 +85,10 @@ class DbConn { $result = $this->do_query($query); if (!$result) return null; $x = array(); - if (MYSQLI) { - while ($obj = $result->fetch_object($classname)) { - $x[] = $obj; - } - $result->free(); - } else { - while ($obj = mysql_fetch_object($result, $classname)) { - $x[] = $obj; - } - mysql_free_result($result); + while ($obj = $result->fetch_object($classname)) { + $x[] = $obj; } + $result->free(); return $x; } @@ -117,13 +98,8 @@ class DbConn { if (!$result) { return null; } - if (MYSQLI) { - $obj = $result->fetch_object($classname); - $result->free(); - } else { - $obj = mysql_fetch_object($result, $classname); - mysql_free_result($result); - } + $obj = $result->fetch_object($classname); + $result->free(); return $obj; } @@ -140,17 +116,10 @@ class DbConn { $result = $this->do_query($query); if (!$result) return null; $x = array(); - if (MYSQLI) { - while ($obj = $result->fetch_object($classname)) { - $x[] = $obj; - } - $result->free(); - } else { - while ($obj = mysql_fetch_object($result, $classname)) { - $x[] = $obj; - } - mysql_free_result($result); + while ($obj = $result->fetch_object($classname)) { + $x[] = $obj; } + $result->free(); return $x; } @@ -192,35 +161,21 @@ class DbConn { return $this->do_query($query); } function insert_id() { - if (MYSQLI) { - return $this->db_conn->insert_id; - } else { - return mysql_insert_id($this->db_conn); - } + return $this->db_conn->insert_id; } function get_int($query, $field) { $result = $this->do_query($query); if (!$result) error_page("database error on query $query"); - if (MYSQLI) { - $x = $result->fetch_object("StdClass"); - $result->free(); - } else { - $x = mysql_fetch_object($result); - mysql_free_result($result); - } + $x = $result->fetch_object("StdClass"); + $result->free(); if ($x) return $x->$field; return false; } function get_double($query, $field) { $result = $this->do_query($query); if (!$result) error_page("database error on query $query"); - if (MYSQLI) { - $x = $result->fetch_object("StdClass"); - $result->free(); - } else { - $x = mysql_fetch_object($result); - mysql_free_result($result); - } + $x = $result->fetch_object("StdClass"); + $result->free(); if ($x) return (double)$x->$field; return false; } @@ -248,25 +203,13 @@ class DbConn { return $this->do_query($query); } function base_escape_string($string) { - if (MYSQLI) { - return $this->db_conn->escape_string($string); - } else { - return mysql_real_escape_string($string); - } + return $this->db_conn->escape_string($string); } function base_error() { - if (MYSQLI) { - return $this->db_conn->error; - } else { - return mysql_error($this->db_conn); - } + return $this->db_conn->error; } function base_errno() { - if (MYSQLI) { - return $this->db_conn->errno; - } else { - return mysql_errno($this->db_conn); - } + return $this->db_conn->errno; } function table_exists($table_name) { $result = $this->do_query("show tables from DBNAME like '$table_name'"); From 2638531fb38fd8ebe531bc4ddd78024d5b5e494c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 19 Jul 2024 12:39:50 -0700 Subject: [PATCH 2/4] PHP DB API: db_host defaults (explicitly) to localhost. Remove the 'use $db_name' query; don't need that with mysqli --- html/inc/boinc_db.inc | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/html/inc/boinc_db.inc b/html/inc/boinc_db.inc index 5e77cd1049d..f3331ef942b 100644 --- a/html/inc/boinc_db.inc +++ b/html/inc/boinc_db.inc @@ -57,31 +57,27 @@ class BoincDb extends DbConn { if ($host && $name && $user && $passwd) { $retval = $instance->init_conn($user, $passwd, $host, $name); if ($retval) { - if ($instance->do_query("use $name")) { - //error_log("BoincDb::get_aux(): connected to replica DB $dbnum"); - self::$instance = $instance; - return; - } + //error_log("BoincDb::get_aux(): connected to replica DB $dbnum"); + self::$instance = $instance; + return; } } // if can't connect to replica, fall through and try DB 0 } $host = parse_config($config, ''); + if (!$host) $host = 'localhost'; $user = parse_config($config, ''); $name = parse_config($config, ''); $passwd = parse_config($config, ''); - // OK if host is null; it will use localhost if (!$name || !$user || !$passwd) { error_log("BoincDb::get_aux(): must specify DB name, user, passwd"); return; } $retval = $instance->init_conn($user, $passwd, $host, $name); if ($retval) { - if ($instance->do_query("use $name")) { - //error_log("BoincDb::get_aux(): connected to DB $dbnum"); - self::$instance = $instance; - return; - } + //error_log("BoincDb::get_aux(): connected to DB $dbnum"); + self::$instance = $instance; + return; } error_log("BoincDb::get_aux(): Couldn't connect to DB $dbnum"); } @@ -95,7 +91,7 @@ class BoincDb extends DbConn { // at the top of it. // // Note: true == 1. - // You can also 2, 3... to select other replicas + // You can also use 2, 3... to select other replicas // static function get($dbnum = 0) { global $generating_xml; From 4b4ba9dbf83384f272f083875cb7f187bc6f6f67 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 1 Aug 2024 14:24:45 -0700 Subject: [PATCH 3/4] A couple of additions to the BoincDb class: - a close() method, closes the connection - a $dbnum member: which replica you're connected to (0 if main DB) Also, BoincDb doesn't inherit DbConn. --- html/inc/boinc_db.inc | 50 ++++++++++++++++++++++++++++--------------- html/inc/db_conn.inc | 10 +++++++-- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/html/inc/boinc_db.inc b/html/inc/boinc_db.inc index f3331ef942b..7b169be3208 100644 --- a/html/inc/boinc_db.inc +++ b/html/inc/boinc_db.inc @@ -1,7 +1,7 @@ . +// A project can have one or more BOINC databases: +// DB 0: +// the main DB; read/write +// identified in config file by db_host, db_name, db_user, db_passwd +// db_host defaults to localhost +// DB 1: +// read-only replica; identified by +// replica_db_host/name/user/passwd (must include all) +// DB 2: +// read-only replica; identified by +// replica2_db_host/name/user/passwd (must include all) +// ... and potentially more + function incs() { $d = dirname(__FILE__); require_once("$d/db_conn.inc"); @@ -24,21 +37,15 @@ function incs() { incs(); -class BoincDb extends DbConn { - static $instance; - - // A project can have one or more databases: - // DB 0: - // the main DB; read/write - // identified in config file by db_host, db_name, db_user, db_passwd - // db_host defaults to localhost - // DB 1: - // read-only replica; identified by - // replica_db_host/name/user/passwd (must include all) - // DB 2: - // read-only replica; identified by - // replica2_db_host/name/user/passwd (must include all) - // ... and potentially more +// class BoincDb represents a connection to a BOINC database. +// All its members are static, so there's only 1 connection at a time. +// get(n) establishes a connection to DB n, +// or DB 0 if that fails or doesn't exit. +// close() closes the connection. + +class BoincDb { + static $instance; // a DbConn object, or null + static $dbnum; // which replica we're connected to // connect to DB $dbnum (0, 1, ...) // If the requested DB doesn't exist or connection fails, connect to DB 0. @@ -59,6 +66,7 @@ class BoincDb extends DbConn { if ($retval) { //error_log("BoincDb::get_aux(): connected to replica DB $dbnum"); self::$instance = $instance; + self::$dbnum = $dbnum; return; } } @@ -77,6 +85,7 @@ class BoincDb extends DbConn { if ($retval) { //error_log("BoincDb::get_aux(): connected to DB $dbnum"); self::$instance = $instance; + self::$dbnum = 0; return; } error_log("BoincDb::get_aux(): Couldn't connect to DB $dbnum"); @@ -116,6 +125,13 @@ class BoincDb extends DbConn { } } + static function close() { + if (isset(self::$instance)) { + self::$instance->close(); + self::$instance = null; + } + } + static function escape_string($string) { if (!$string) return ''; $db = self::get(); diff --git a/html/inc/db_conn.inc b/html/inc/db_conn.inc index 11d2573b063..910d2f9af9a 100644 --- a/html/inc/db_conn.inc +++ b/html/inc/db_conn.inc @@ -22,8 +22,8 @@ require_once("../inc/db.inc"); // Intended to be subclassed (e.g., BoincDb, BossaDb) // class DbConn { - var $db_conn; - var $db_name; + var $db_conn; // a mysqli object + var $db_name; // the DB name function init_conn($user, $passwd, $host, $name) { $x = explode(":", $host); @@ -54,6 +54,12 @@ class DbConn { return true; } + function close() { + if ($this->db_conn) { + $this->db_conn->close(); + } + } + // in keeping with PHP/MySQL convention, return true (nonzero) on success. // (This is the opposite of the BOINC convention) // From da1727c64fc7f8538b9e97c6f28bc0ff8dd1d556 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 17 Aug 2024 12:17:25 -0700 Subject: [PATCH 4/4] BoincDb::get(): if open to different dbnum, close and reconnect. Don't throw exception if connect fails --- html/inc/boinc_db.inc | 5 ++++- html/inc/db_conn.inc | 12 ++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/html/inc/boinc_db.inc b/html/inc/boinc_db.inc index 7b169be3208..ff3b4c20d4b 100644 --- a/html/inc/boinc_db.inc +++ b/html/inc/boinc_db.inc @@ -105,7 +105,10 @@ class BoincDb { static function get($dbnum = 0) { global $generating_xml; if (isset(self::$instance)) { - return self::$instance; + if (self::$dbnum == $dbnum) { + return self::$instance; + } + close(); } if (web_stopped()) { if ($generating_xml) { diff --git a/html/inc/db_conn.inc b/html/inc/db_conn.inc index 910d2f9af9a..b39ae8d36f5 100644 --- a/html/inc/db_conn.inc +++ b/html/inc/db_conn.inc @@ -33,14 +33,10 @@ class DbConn { } else { $port = null; } - if (1) { // don't use persistent connections for now - $this->db_conn = @new mysqli( - $host, $user, $passwd, $name, $port - ); - } else { - $this->db_conn = @new mysqli( - 'p:'.$host, $user, $passwd, $name, $port - ); + try { + $this->db_conn = @new mysqli($host, $user, $passwd, $name, $port); + } catch(Exception $e) { + return false; } if (mysqli_connect_error()) { return false;