mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
MDL-19711 dml: Enable use of readonly slave database handles
Implemented with moodle_read_slave_trait Functionality is triggered by supplying config dboption['readonly']. See config-dist.php for more info on supported dboptions. pgsql and mysqli drivers are using this feature. Also added support for connection timeout for these two drivers.
This commit is contained in:
parent
d85118369d
commit
46cfde3d95
19 changed files with 2422 additions and 28 deletions
|
@ -108,13 +108,13 @@ abstract class moodle_database {
|
|||
/** @var float Last time in seconds with millisecond precision. */
|
||||
protected $last_time;
|
||||
/** @var bool Flag indicating logging of query in progress. This helps prevent infinite loops. */
|
||||
private $loggingquery = false;
|
||||
protected $loggingquery = false;
|
||||
|
||||
/** @var bool True if the db is used for db sessions. */
|
||||
protected $used_for_db_sessions = false;
|
||||
|
||||
/** @var array Array containing open transactions. */
|
||||
private $transactions = array();
|
||||
protected $transactions = array();
|
||||
/** @var bool Flag used to force rollback of all current transactions. */
|
||||
private $force_rollback = false;
|
||||
|
||||
|
@ -2717,6 +2717,22 @@ abstract class moodle_database {
|
|||
return $this->reads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we want to connect to slave database for read queries.
|
||||
* @return bool Want read only connection
|
||||
*/
|
||||
public function want_read_slave(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of reads before first write done by this database.
|
||||
* @return int Number of reads.
|
||||
*/
|
||||
public function perf_get_reads_slave(): int {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of writes done by this database.
|
||||
* @return int Number of writes.
|
||||
|
|
393
lib/dml/moodle_read_slave_trait.php
Normal file
393
lib/dml/moodle_read_slave_trait.php
Normal file
|
@ -0,0 +1,393 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Trait that adds read-only slave connection capability
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Trait to wrap connect() method of database driver classes that gives
|
||||
* ability to use read only slave instances for SELECT queries. For the
|
||||
* databases that support replication and read only connections to the slave.
|
||||
* If the slave connection is configured there will be two database handles
|
||||
* created, one for the master and another one for the slave. If there's no
|
||||
* slave specified everything uses master handle.
|
||||
*
|
||||
* Classes that use this trait need to rename existing connect() method to
|
||||
* raw_connect(). In addition, they need to provide get_db_handle() and
|
||||
* set_db_handle() methods, due to dbhandle attributes not being named
|
||||
* consistently across the database driver classes.
|
||||
*
|
||||
* Read only slave connection is configured in the $CFG->dboptions['readonly']
|
||||
* array.
|
||||
* - It supports multiple 'instance' entries, in case one is not accessible,
|
||||
* but only one (first connectable) instance is used.
|
||||
* - 'latency' option: master -> slave sync latency in seconds (will probably
|
||||
* be a fraction of a second). If specified, a table being written to is
|
||||
* deemed fully synced and suitable for slave read.
|
||||
* - 'exclude_tables' option: a list of tables that never go to the slave for
|
||||
* querying. The feature is meant to be used in emergency only, so the
|
||||
* readonly feature can still be used in case there is a rogue query that
|
||||
* does not go through the standard dml interface or some other unaccounted
|
||||
* situation. It should not be used under normal circumstances, and its use
|
||||
* indicates a problem in the system that needs addressig.
|
||||
*
|
||||
* Choice of the database handle is based on following:
|
||||
* - SQL_QUERY_INSERT, UPDATE and STRUCTURE record table from the query
|
||||
* in the $written array and microtime() the event if the 'latency' option
|
||||
* is set. For those queries master write handle is used.
|
||||
* - SQL_QUERY_AUX queries will always use the master write handle because they
|
||||
* are used for transactionstart/end, locking etc. In that respect, query_start() and
|
||||
* query_end() *must not* be used during the connection phase.
|
||||
* - SELECT queries will use the master write handle if:
|
||||
* -- any of the tables involved is a temp table
|
||||
* -- any of the tables involved is listed in the 'exclude_tables' option
|
||||
* -- any of the tables involved is in the $written array:
|
||||
* * If the 'latency' option is set then the microtime() is compared to
|
||||
* the write microrime, and if more then latency time has passed the slave
|
||||
* handle is used.
|
||||
* * Otherwise (not enough time passed or 'latency' option not set)
|
||||
* we choose the master write handle
|
||||
* If none of the above conditions are met the slave instance is used.
|
||||
*
|
||||
* A 'latency' example:
|
||||
* - we have set $CFG->dboptions['readonly']['latency'] to 0.2.
|
||||
* - a SQL_QUERY_UPDATE to table tbl_x happens, and it is recorded in
|
||||
* the $written array
|
||||
* - 0.15 seconds later SQL_QUERY_SELECT with tbl_x is requested - the master
|
||||
* connection is used
|
||||
* - 0.10 seconds later (0.25 seconds after SQL_QUERY_UPDATE) another
|
||||
* SQL_QUERY_SELECT with tbl_x is requested - this time more than 0.2 secs
|
||||
* has gone and master -> slave sync is assumed, so the slave connection is
|
||||
* used again
|
||||
*/
|
||||
|
||||
trait moodle_read_slave_trait {
|
||||
|
||||
/** @var resource master write database handle */
|
||||
protected $dbhwrite;
|
||||
|
||||
/** @var resource slave read only database handle */
|
||||
protected $dbhreadonly;
|
||||
|
||||
private $wantreadslave = false;
|
||||
private $readsslave = 0;
|
||||
private $slavelatency = 0;
|
||||
|
||||
private $written = []; // Track tables being written to.
|
||||
private $readexclude = []; // Tables to exclude from using dbhreadonly.
|
||||
|
||||
// Store original params.
|
||||
private $pdbhost;
|
||||
private $pdbuser;
|
||||
private $pdbpass;
|
||||
private $pdbname;
|
||||
private $pprefix;
|
||||
private $pdboptions;
|
||||
|
||||
/**
|
||||
* Gets db handle currently used with queries
|
||||
* @return resource
|
||||
*/
|
||||
abstract protected function get_db_handle();
|
||||
|
||||
/**
|
||||
* Sets db handle to be used with subsequent queries
|
||||
* @param resource $dbh
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function set_db_handle($dbh): void;
|
||||
|
||||
/**
|
||||
* Connect to db
|
||||
* The real connection establisment, called from connect() and set_dbhwrite()
|
||||
* @param string $dbhost The database host.
|
||||
* @param string $dbuser The database username.
|
||||
* @param string $dbpass The database username's password.
|
||||
* @param string $dbname The name of the database being connected to.
|
||||
* @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
|
||||
* @param array $dboptions driver specific options
|
||||
* @return bool true
|
||||
* @throws dml_connection_exception if error
|
||||
*/
|
||||
abstract protected function raw_connect(string $dbhost, string $dbuser, string $dbpass, string $dbname, $prefix, array $dboptions = null): bool;
|
||||
|
||||
/**
|
||||
* Connect to db
|
||||
* The connection parameters processor that sets up stage for master write and slave readonly handles.
|
||||
* Must be called before other methods.
|
||||
* @param string $dbhost The database host.
|
||||
* @param string $dbuser The database username.
|
||||
* @param string $dbpass The database username's password.
|
||||
* @param string $dbname The name of the database being connected to.
|
||||
* @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used
|
||||
* @param array $dboptions driver specific options
|
||||
* @return bool true
|
||||
* @throws dml_connection_exception if error
|
||||
*/
|
||||
public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions = null) {
|
||||
$this->pdbhost = $dbhost;
|
||||
$this->pdbuser = $dbuser;
|
||||
$this->pdbpass = $dbpass;
|
||||
$this->pdbname = $dbname;
|
||||
$this->pprefix = $prefix;
|
||||
$this->pdboptions = $dboptions;
|
||||
|
||||
if ($dboptions) {
|
||||
if (isset($dboptions['readonly'])) {
|
||||
$this->wantreadslave = true;
|
||||
$dboptionsro = $dboptions['readonly'];
|
||||
|
||||
if (isset($dboptionsro['connecttimeout'])) {
|
||||
$dboptions['connecttimeout'] = $dboptionsro['connecttimeout'];
|
||||
} else if (!isset($dboptions['connecttimeout'])) {
|
||||
$dboptions['connecttimeout'] = 2; // Default readonly connection timeout.
|
||||
}
|
||||
if (isset($dboptionsro['latency'])) {
|
||||
$this->slavelatency = $dboptionsro['latency'];
|
||||
}
|
||||
if (isset($dboptionsro['exclude_tables'])) {
|
||||
$this->readexclude = $dboptionsro['exclude_tables'];
|
||||
if (!is_array($this->readexclude)) {
|
||||
throw new configuration_exception('exclude_tables must be an array');
|
||||
}
|
||||
}
|
||||
$dbport = isset($dboptions['dbport']) ? $dboptions['dbport'] : null;
|
||||
|
||||
$slaves = $dboptionsro['instance'];
|
||||
if (!is_array($slaves) || !isset($slaves[0])) {
|
||||
$slaves = [$slaves];
|
||||
}
|
||||
|
||||
if (count($slaves) > 1) {
|
||||
// Randomise things a bit.
|
||||
shuffle($slaves);
|
||||
}
|
||||
|
||||
// Find first connectable readonly slave.
|
||||
$rodb = [];
|
||||
foreach ($slaves as $slave) {
|
||||
if (!is_array($slave)) {
|
||||
$slave = ['dbhost' => $slave];
|
||||
}
|
||||
foreach (['dbhost', 'dbuser', 'dbpass'] as $dbparam) {
|
||||
$rodb[$dbparam] = isset($slave[$dbparam]) ? $slave[$dbparam] : $$dbparam;
|
||||
}
|
||||
$dboptions['dbport'] = isset($slave['dbport']) ? $slave['dbport'] : $dbport;
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
try {
|
||||
$this->raw_connect($rodb['dbhost'], $rodb['dbuser'], $rodb['dbpass'], $dbname, $prefix, $dboptions);
|
||||
$this->dbhreadonly = $this->get_db_handle();
|
||||
break;
|
||||
} catch (dml_connection_exception $e) {
|
||||
// If readonly slave is not connectable we'll have to do without it.
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
// ... lock_db queries always go to master.
|
||||
// Since it is a lock and as such marshalls concurrent connections,
|
||||
// it is best to leave it out and avoid master/slave latency.
|
||||
$this->readexclude[] = 'lock_db';
|
||||
// ... and sessions.
|
||||
$this->readexclude[] = 'sessions';
|
||||
}
|
||||
}
|
||||
if (!$this->dbhreadonly) {
|
||||
$this->set_dbhwrite();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database handle to readwrite master
|
||||
* Will connect if required. Calls set_db_handle()
|
||||
* @return void
|
||||
*/
|
||||
private function set_dbhwrite(): void {
|
||||
// Late connect to read/write master if needed.
|
||||
if (!$this->dbhwrite) {
|
||||
$this->raw_connect($this->pdbhost, $this->pdbuser, $this->pdbpass, $this->pdbname, $this->pprefix, $this->pdboptions);
|
||||
$this->dbhwrite = $this->get_db_handle();
|
||||
}
|
||||
$this->set_db_handle($this->dbhwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we want to connect to slave database for read queries.
|
||||
* @return bool Want read only connection
|
||||
*/
|
||||
public function want_read_slave(): bool {
|
||||
return $this->wantreadslave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of reads done by the read only database.
|
||||
* @return int Number of reads.
|
||||
*/
|
||||
public function perf_get_reads_slave(): int {
|
||||
return $this->readsslave;
|
||||
}
|
||||
|
||||
/**
|
||||
* On DBs that support it, switch to transaction mode and begin a transaction
|
||||
* @return moodle_transaction
|
||||
*/
|
||||
public function start_delegated_transaction() {
|
||||
$this->set_dbhwrite();
|
||||
return parent::start_delegated_transaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before each db query.
|
||||
* @param string $sql
|
||||
* @param array $params array of parameters
|
||||
* @param int $type type of query
|
||||
* @param mixed $extrainfo driver specific extra information
|
||||
* @return void
|
||||
*/
|
||||
protected function query_start($sql, array $params = null, $type, $extrainfo = null) {
|
||||
parent::query_start($sql, $params, $type, $extrainfo);
|
||||
$this->select_db_handle($type, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select appropriate db handle - readwrite or readonly
|
||||
* @param int $type type of query
|
||||
* @param string $sql
|
||||
* @return void
|
||||
*/
|
||||
protected function select_db_handle(int $type, string $sql): void {
|
||||
if ($this->dbhreadonly && $this->can_use_readonly($type, $sql)) {
|
||||
$this->readsslave++;
|
||||
$this->set_db_handle($this->dbhreadonly);
|
||||
return;
|
||||
}
|
||||
$this->set_dbhwrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if The query qualifies for readonly connection execution
|
||||
* Logging queries are exempt, those are write operations that circumvent
|
||||
* standard query_start/query_end paths.
|
||||
* @param int $type type of query
|
||||
* @param string $sql
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_use_readonly(int $type, string $sql): bool {
|
||||
if ($this->loggingquery) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (during_initial_install()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Transactions are done as AUX, we cannot play with that.
|
||||
switch ($type) {
|
||||
case SQL_QUERY_SELECT:
|
||||
if ($this->transactions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = null;
|
||||
foreach ($this->table_names($sql) as $tablename) {
|
||||
if (in_array($tablename, $this->readexclude)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->temptables && $this->temptables->is_temptable($tablename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($this->written[$tablename])) {
|
||||
if ($this->slavelatency) {
|
||||
$now = $now ?: microtime(true);
|
||||
if ($now - $this->written[$tablename] < $this->slavelatency) {
|
||||
return false;
|
||||
}
|
||||
unset($this->written[$tablename]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
case SQL_QUERY_INSERT:
|
||||
case SQL_QUERY_UPDATE:
|
||||
// If we are in transaction we cannot set the written time yet.
|
||||
$now = $this->slavelatency && !$this->transactions ? microtime(true) : true;
|
||||
foreach ($this->table_names($sql) as $tablename) {
|
||||
$this->written[$tablename] = $now;
|
||||
}
|
||||
return false;
|
||||
case SQL_QUERY_STRUCTURE:
|
||||
foreach ($this->table_names($sql) as $tablename) {
|
||||
if (!in_array($tablename, $this->readexclude)) {
|
||||
$this->readexclude[] = $tablename;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates delegated transaction finished successfully.
|
||||
* Set written times after outermost transaction finished
|
||||
* @param moodle_transaction $transaction The transaction to commit
|
||||
* @return void
|
||||
* @throws dml_transaction_exception Creates and throws transaction related exceptions.
|
||||
*/
|
||||
public function commit_delegated_transaction(moodle_transaction $transaction) {
|
||||
parent::commit_delegated_transaction($transaction);
|
||||
|
||||
if ($this->transactions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->slavelatency) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = null;
|
||||
foreach ($this->written as $tablename => $when) {
|
||||
if ($when === true) {
|
||||
$now = $now ?: microtime(true);
|
||||
$this->written[$tablename] = $now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse table names from query
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
protected function table_names(string $sql): array {
|
||||
preg_match_all('/\b'.$this->prefix.'([a-z][A-Za-z0-9_]*)/', $sql, $match);
|
||||
return $match[1];
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/moodle_database.php');
|
||||
require_once(__DIR__.'/moodle_read_slave_trait.php');
|
||||
require_once(__DIR__.'/mysqli_native_moodle_recordset.php');
|
||||
require_once(__DIR__.'/mysqli_native_moodle_temptables.php');
|
||||
|
||||
|
@ -36,6 +37,9 @@ require_once(__DIR__.'/mysqli_native_moodle_temptables.php');
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mysqli_native_moodle_database extends moodle_database {
|
||||
use moodle_read_slave_trait {
|
||||
can_use_readonly as read_slave_can_use_readonly;
|
||||
}
|
||||
|
||||
/** @var mysqli $mysqli */
|
||||
protected $mysqli = null;
|
||||
|
@ -235,6 +239,14 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
if (isset($this->dboptions['dbcollation'])) {
|
||||
return $this->dboptions['dbcollation'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set 'dbcollation' option
|
||||
*
|
||||
* @return string $dbcollation
|
||||
*/
|
||||
private function detect_collation(): string {
|
||||
if ($this->external) {
|
||||
return null;
|
||||
}
|
||||
|
@ -246,9 +258,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
$sql = "SELECT collation_name
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config' AND column_name = 'value'";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($rec = $result->fetch_assoc()) {
|
||||
// MySQL 8 BC: information_schema.* returns the fields in upper case.
|
||||
$rec = array_change_key_case($rec, CASE_LOWER);
|
||||
|
@ -260,9 +270,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
if (!$collation) {
|
||||
// Get the default database collation, but only if using UTF-8.
|
||||
$sql = "SELECT @@collation_database";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($rec = $result->fetch_assoc()) {
|
||||
if (strpos($rec['@@collation_database'], 'utf8_') === 0 || strpos($rec['@@collation_database'], 'utf8mb4_') === 0) {
|
||||
$collation = $rec['@@collation_database'];
|
||||
|
@ -275,9 +283,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
// We want only utf8 compatible collations.
|
||||
$collation = null;
|
||||
$sql = "SHOW COLLATION WHERE Collation LIKE 'utf8mb4\_%' AND Charset = 'utf8mb4'";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
while ($res = $result->fetch_assoc()) {
|
||||
$collation = $res['Collation'];
|
||||
if (strtoupper($res['Default']) === 'YES') {
|
||||
|
@ -518,7 +524,6 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
|
||||
/**
|
||||
* Connect to db
|
||||
* Must be called before other methods.
|
||||
* @param string $dbhost The database host.
|
||||
* @param string $dbuser The database username.
|
||||
* @param string $dbpass The database username's password.
|
||||
|
@ -527,7 +532,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
* @param array $dboptions driver specific options
|
||||
* @return bool success
|
||||
*/
|
||||
public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
|
||||
public function raw_connect(string $dbhost, string $dbuser, string $dbpass, string $dbname, $prefix, array $dboptions=null): bool {
|
||||
$driverstatus = $this->driver_installed();
|
||||
|
||||
if ($driverstatus !== true) {
|
||||
|
@ -556,10 +561,19 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
if ($dbhost and !empty($this->dboptions['dbpersist'])) {
|
||||
$dbhost = "p:$dbhost";
|
||||
}
|
||||
$this->mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket);
|
||||
$this->mysqli = mysqli_init();
|
||||
if (!empty($this->dboptions['connecttimeout'])) {
|
||||
$this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->dboptions['connecttimeout']);
|
||||
}
|
||||
|
||||
if ($this->mysqli->connect_errno !== 0) {
|
||||
$dberr = $this->mysqli->connect_error;
|
||||
$conn = null;
|
||||
try {
|
||||
$conn = $this->mysqli->real_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket);
|
||||
} catch (\Exception $e) {
|
||||
$dberr = "$e";
|
||||
}
|
||||
if (!$conn) {
|
||||
$dberr = $dberr ?: $this->mysqli->connect_error;
|
||||
$this->mysqli = null;
|
||||
throw new dml_connection_exception($dberr);
|
||||
}
|
||||
|
@ -568,16 +582,14 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
$this->query_log_prevent();
|
||||
|
||||
if (isset($dboptions['dbcollation'])) {
|
||||
$collationinfo = explode('_', $dboptions['dbcollation']);
|
||||
$this->dboptions['dbcollation'] = $dboptions['dbcollation'];
|
||||
$collation = $this->dboptions['dbcollation'] = $dboptions['dbcollation'];
|
||||
} else {
|
||||
$collationinfo = explode('_', $this->get_dbcollation());
|
||||
$collation = $this->detect_collation();
|
||||
}
|
||||
$collationinfo = explode('_', $collation);
|
||||
$charset = reset($collationinfo);
|
||||
|
||||
$this->query_start("--set_charset()", null, SQL_QUERY_AUX);
|
||||
$this->mysqli->set_charset($charset);
|
||||
$this->query_end(true);
|
||||
|
||||
// If available, enforce strict mode for the session. That guaranties
|
||||
// standard behaviour under some situations, avoiding some MySQL nasty
|
||||
|
@ -587,9 +599,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
$si = $this->get_server_info();
|
||||
if (version_compare($si['version'], '5.0.2', '>=')) {
|
||||
$sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES'";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
}
|
||||
|
||||
// We can enable logging now.
|
||||
|
@ -614,6 +624,40 @@ class mysqli_native_moodle_database extends moodle_database {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets db handle currently used with queries
|
||||
* @return resource
|
||||
*/
|
||||
protected function get_db_handle() {
|
||||
return $this->mysqli;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets db handle to be used with subsequent queries
|
||||
* @param resource $dbh
|
||||
* @return void
|
||||
*/
|
||||
protected function set_db_handle($dbh): void {
|
||||
$this->mysqli = $dbh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if The query qualifies for readonly connection execution
|
||||
* Logging queries are exempt, those are write operations that circumvent
|
||||
* standard query_start/query_end paths.
|
||||
* @param int $type type of query
|
||||
* @param string $sql
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_use_readonly(int $type, string $sql): bool {
|
||||
// ... *_LOCK queries always go to master.
|
||||
if (preg_match('/\b(GET|RELEASE)_LOCK/i', $sql)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->read_slave_can_use_readonly($type, $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns database server info array
|
||||
* @return array Array containing 'description' and 'version' info
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/moodle_database.php');
|
||||
require_once(__DIR__.'/moodle_read_slave_trait.php');
|
||||
require_once(__DIR__.'/pgsql_native_moodle_recordset.php');
|
||||
require_once(__DIR__.'/pgsql_native_moodle_temptables.php');
|
||||
|
||||
|
@ -36,6 +37,14 @@ require_once(__DIR__.'/pgsql_native_moodle_temptables.php');
|
|||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class pgsql_native_moodle_database extends moodle_database {
|
||||
use moodle_read_slave_trait {
|
||||
select_db_handle as read_slave_select_db_handle;
|
||||
can_use_readonly as read_slave_can_use_readonly;
|
||||
query_start as read_slave_query_start;
|
||||
}
|
||||
|
||||
/** @var array $dbhcursor keep track of open cursors */
|
||||
private $dbhcursor = [];
|
||||
|
||||
/** @var resource $pgsql database resource */
|
||||
protected $pgsql = null;
|
||||
|
@ -110,7 +119,6 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
|
||||
/**
|
||||
* Connect to db
|
||||
* Must be called before other methods.
|
||||
* @param string $dbhost The database host.
|
||||
* @param string $dbuser The database username.
|
||||
* @param string $dbpass The database username's password.
|
||||
|
@ -120,7 +128,7 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
* @return bool true
|
||||
* @throws dml_connection_exception if error
|
||||
*/
|
||||
public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
|
||||
public function raw_connect(string $dbhost, string $dbuser, string $dbpass, string $dbname, $prefix, array $dboptions=null): bool {
|
||||
if ($prefix == '' and !$this->external) {
|
||||
//Enforce prefixes for everybody but mysql
|
||||
throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
|
||||
|
@ -160,6 +168,10 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
$connection = "host='$this->dbhost' $port user='$this->dbuser' password='$pass' dbname='$this->dbname'";
|
||||
}
|
||||
|
||||
if (!empty($this->dboptions['connecttimeout'])) {
|
||||
$connection .= " connect_timeout=".$this->dboptions['connecttimeout'];
|
||||
}
|
||||
|
||||
if (empty($this->dboptions['dbhandlesoptions'])) {
|
||||
// ALTER USER and ALTER DATABASE are overridden by these settings.
|
||||
$options = array('--client_encoding=utf8', '--standard_conforming_strings=on');
|
||||
|
@ -232,6 +244,64 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets db handle currently used with queries
|
||||
* @return resource
|
||||
*/
|
||||
protected function get_db_handle() {
|
||||
return $this->pgsql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets db handle to be used with subsequent queries
|
||||
* @param resource $dbh
|
||||
* @return void
|
||||
*/
|
||||
protected function set_db_handle($dbh): void {
|
||||
$this->pgsql = $dbh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select appropriate db handle - readwrite or readonly
|
||||
* @param int $type type of query
|
||||
* @param string $sql
|
||||
* @return void
|
||||
*/
|
||||
protected function select_db_handle(int $type, string $sql): void {
|
||||
$this->read_slave_select_db_handle($type, $sql);
|
||||
|
||||
if (preg_match('/^DECLARE (crs\w*) NO SCROLL CURSOR/', $sql, $match)) {
|
||||
$cursor = $match[1];
|
||||
$this->dbhcursor[$cursor] = $this->pgsql;
|
||||
}
|
||||
if (preg_match('/^(?:FETCH \d+ FROM|CLOSE) (crs\w*)\b/', $sql, $match)) {
|
||||
$cursor = $match[1];
|
||||
$this->pgsql = $this->dbhcursor[$cursor];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if The query qualifies for readonly connection execution
|
||||
* Logging queries are exempt, those are write operations that circumvent
|
||||
* standard query_start/query_end paths.
|
||||
* @param int $type type of query
|
||||
* @param string $sql
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_use_readonly(int $type, string $sql): bool {
|
||||
// ... pg_*lock queries always go to master.
|
||||
if (preg_match('/\bpg_\w*lock/', $sql)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... a nuisance - temptables use this.
|
||||
if (preg_match('/\bpg_constraint/', $sql) && $this->temptables->get_temptables()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->read_slave_can_use_readonly($type, $sql);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before each db query.
|
||||
|
@ -242,8 +312,8 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
* @return void
|
||||
*/
|
||||
protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
|
||||
parent::query_start($sql, $params, $type, $extrainfo);
|
||||
// pgsql driver tents to send debug to output, we do not need that ;-)
|
||||
$this->read_slave_query_start($sql, $params, $type, $extrainfo);
|
||||
// pgsql driver tends to send debug to output, we do not need that.
|
||||
$this->last_error_reporting = error_reporting(0);
|
||||
}
|
||||
|
||||
|
@ -733,8 +803,6 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
|
||||
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
|
||||
|
||||
$this->query_start($sql, $params, SQL_QUERY_SELECT);
|
||||
|
||||
// For any query that doesn't explicitly specify a limit, we must use cursors to stop it
|
||||
// loading the entire thing (unless the config setting is turned off).
|
||||
$usecursors = !$limitnum && ($this->get_fetch_buffer_size() > 0);
|
||||
|
@ -747,12 +815,14 @@ class pgsql_native_moodle_database extends moodle_database {
|
|||
|
||||
// Do the query to a cursor.
|
||||
$sql = 'DECLARE ' . $cursorname . ' NO SCROLL CURSOR WITH HOLD FOR ' . $sql;
|
||||
$result = pg_query_params($this->pgsql, $sql, $params);
|
||||
} else {
|
||||
$result = pg_query_params($this->pgsql, $sql, $params);
|
||||
$cursorname = '';
|
||||
}
|
||||
|
||||
$this->query_start($sql, $params, SQL_QUERY_SELECT);
|
||||
|
||||
$result = pg_query_params($this->pgsql, $sql, $params);
|
||||
|
||||
$this->query_end($result);
|
||||
if ($usecursors) {
|
||||
pg_free_result($result);
|
||||
|
|
87
lib/dml/tests/dml_mysqli_read_slave_test.php
Normal file
87
lib/dml/tests/dml_mysqli_read_slave_test.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* DML read/read-write database handle tests for mysqli_native_moodle_database
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/fixtures/read_slave_moodle_database_mock_mysqli.php');
|
||||
|
||||
/**
|
||||
* DML mysqli_native_moodle_database read slave specific tests
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_dml_mysqli_read_slave_testcase extends base_testcase {
|
||||
/**
|
||||
* Test readonly handle is not used for reading from special pg_*() call queries,
|
||||
* pg_try_advisory_lock and pg_advisory_unlock.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_lock() : void {
|
||||
$DB = new read_slave_moodle_database_mock_mysqli();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
$DB->query_start("SELECT GET_LOCK('lock',1)", null, SQL_QUERY_SELECT);
|
||||
$this->assertEquals('test_rw', $DB->get_db_handle());
|
||||
$DB->query_end(null);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
$DB->query_start("SELECT RELEASE_LOCK('lock',1)", null, SQL_QUERY_SELECT);
|
||||
$this->assertEquals('test_rw', $DB->get_db_handle());
|
||||
$DB->query_end(null);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly connection failure with real mysqli connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_real_readslave_connect_fail() : void {
|
||||
global $DB;
|
||||
|
||||
if ($DB->get_dbfamily() != 'mysql') {
|
||||
$this->markTestSkipped("Not mysql");
|
||||
}
|
||||
|
||||
// Open second connection.
|
||||
$cfg = $DB->export_dbconfig();
|
||||
if (!isset($cfg->dboptions)) {
|
||||
$cfg->dboptions = array();
|
||||
}
|
||||
$cfg->dboptions['readonly'] = [
|
||||
'instance' => ['host.that.is.not'],
|
||||
'connecttimeout' => 1
|
||||
];
|
||||
|
||||
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
|
||||
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
|
||||
$this->assertTrue(count($db2->get_records('user')) > 0);
|
||||
}
|
||||
}
|
162
lib/dml/tests/dml_pgsql_read_slave_test.php
Normal file
162
lib/dml/tests/dml_pgsql_read_slave_test.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* DML read/read-write database handle tests for pgsql_native_moodle_database
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/fixtures/read_slave_moodle_database_mock_pgsql.php');
|
||||
|
||||
/**
|
||||
* DML pgsql_native_moodle_database read slave specific tests
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_dml_pgsql_read_slave_testcase extends base_testcase {
|
||||
/**
|
||||
* Test correct database handles are used for cursors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_cursors() : void {
|
||||
$DB = new read_slave_moodle_database_mock_pgsql();
|
||||
|
||||
// Declare a cursor on a table that has not been written to.
|
||||
list($sql, $params, $type) = $DB->fix_sql_params("SELECT * FROM {table}");
|
||||
$sql = "DECLARE crs1 NO SCROLL CURSOR WITH HOLD FOR $sql";
|
||||
$DB->query_start($sql, null, SQL_QUERY_SELECT);
|
||||
$DB->query_end(null);
|
||||
|
||||
// Declare a cursor on a table that has been written to.
|
||||
list($sql, $params, $type) = $DB->fix_sql_params("INSERT INTO {table2} (name) VALUES ('blah')");
|
||||
$DB->query_start($sql, null, SQL_QUERY_INSERT);
|
||||
$DB->query_end(null);
|
||||
list($sql, $params, $type) = $DB->fix_sql_params("SELECT * FROM {table2}");
|
||||
$sql = "DECLARE crs2 NO SCROLL CURSOR WITH HOLD FOR $sql";
|
||||
$DB->query_start($sql, null, SQL_QUERY_SELECT);
|
||||
$DB->query_end(null);
|
||||
|
||||
// Read from the non-written to table cursor.
|
||||
$sql = 'FETCH 1 FROM crs1';
|
||||
$DB->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->assertEquals('test_ro', $DB->get_db_handle());
|
||||
$DB->query_end(null);
|
||||
|
||||
// Read from the written to table cursor.
|
||||
$sql = 'FETCH 1 FROM crs2';
|
||||
$DB->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->assertEquals('test_rw', $DB->get_db_handle());
|
||||
$DB->query_end(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly handle is used for reading from random pg_*() call queries.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_pg_table() : void {
|
||||
$DB = new read_slave_moodle_database_mock_pgsql();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
$DB->query_start('SELECT pg_whatever(1)', null, SQL_QUERY_SELECT);
|
||||
$this->assertEquals('test_ro', $DB->get_db_handle());
|
||||
$DB->query_end(null);
|
||||
$this->assertEquals(1, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly handle is not used for reading from special pg_*() call queries,
|
||||
* pg_try_advisory_lock and pg_advisory_unlock.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_pg_lock_table() : void {
|
||||
$DB = new read_slave_moodle_database_mock_pgsql();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
foreach (['pg_try_advisory_lock', 'pg_advisory_unlock'] as $fn) {
|
||||
$DB->query_start("SELECT $fn(1)", null, SQL_QUERY_SELECT);
|
||||
$this->assertEquals('test_rw', $DB->get_db_handle());
|
||||
$DB->query_end(null);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly handle is not used for reading from temptables
|
||||
* and getting temptables metadata.
|
||||
* This test is only possible because of no pg_query error reporting.
|
||||
* It may need to be removed in the future if we decide to handle null
|
||||
* results in pgsql_native_moodle_database differently.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_temp_table() : void {
|
||||
$DB = new read_slave_moodle_database_mock_pgsql();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
$dbman = $DB->get_manager();
|
||||
$table = new xmldb_table('silly_test_table');
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
|
||||
$table->add_field('msg', XMLDB_TYPE_CHAR, 255);
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
$dbman->create_temp_table($table);
|
||||
|
||||
$DB->get_columns('silly_test_table');
|
||||
$DB->get_records('silly_test_table');
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly connection failure with real pgsql connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_real_readslave_connect_fail() : void {
|
||||
global $DB;
|
||||
|
||||
if ($DB->get_dbfamily() != 'postgres') {
|
||||
$this->markTestSkipped("Not postgres");
|
||||
}
|
||||
|
||||
// Open second connection.
|
||||
$cfg = $DB->export_dbconfig();
|
||||
if (!isset($cfg->dboptions)) {
|
||||
$cfg->dboptions = array();
|
||||
}
|
||||
$cfg->dboptions['readonly'] = [
|
||||
'instance' => ['host.that.is.not'],
|
||||
'connecttimeout' => 1
|
||||
];
|
||||
|
||||
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
|
||||
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
|
||||
$this->assertTrue(count($db2->get_records('user')) > 0);
|
||||
}
|
||||
}
|
432
lib/dml/tests/dml_read_slave_test.php
Normal file
432
lib/dml/tests/dml_read_slave_test.php
Normal file
|
@ -0,0 +1,432 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* DML read/read-write database handle use tests
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/fixtures/read_slave_moodle_database_table_names.php');
|
||||
require_once(__DIR__.'/fixtures/read_slave_moodle_database_special.php');
|
||||
|
||||
/**
|
||||
* DML read/read-write database handle use tests
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_dml_read_slave_testcase extends base_testcase {
|
||||
|
||||
/** @var float */
|
||||
static private $dbreadonlylatency = 0.8;
|
||||
|
||||
/**
|
||||
* Instantiates a test database interface object.
|
||||
*
|
||||
* @param bool $wantlatency
|
||||
* @param mixed $readonly
|
||||
* @param mixed $dbclass
|
||||
* @return read_slave_moodle_database $db
|
||||
*/
|
||||
public function new_db(
|
||||
$wantlatency = false,
|
||||
$readonly = [
|
||||
['dbhost' => 'test_ro1', 'dbport' => 1, 'dbuser' => 'test1', 'dbpass' => 'test1'],
|
||||
['dbhost' => 'test_ro2', 'dbport' => 2, 'dbuser' => 'test2', 'dbpass' => 'test2'],
|
||||
['dbhost' => 'test_ro3', 'dbport' => 3, 'dbuser' => 'test3', 'dbpass' => 'test3'],
|
||||
],
|
||||
$dbclass = read_slave_moodle_database::class
|
||||
) : read_slave_moodle_database {
|
||||
$dbhost = 'test_rw';
|
||||
$dbname = 'test';
|
||||
$dbuser = 'test';
|
||||
$dbpass = 'test';
|
||||
$prefix = 'test_';
|
||||
$dboptions = ['readonly' => ['instance' => $readonly, 'exclude_tables' => ['exclude']]];
|
||||
if ($wantlatency) {
|
||||
$dboptions['readonly']['latency'] = self::$dbreadonlylatency;
|
||||
}
|
||||
|
||||
$db = new $dbclass();
|
||||
$db->connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
|
||||
return $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asert that the mock handle returned from read_slave_moodle_database methods
|
||||
* is a readonly slave handle.
|
||||
*
|
||||
* @param string $handle
|
||||
* @return void
|
||||
*/
|
||||
private function assert_readonly_handle($handle) : void {
|
||||
$this->assertRegExp('/^test_ro\d:\d:test\d:test\d$/', $handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* moodle_read_slave_trait::table_names() test data provider
|
||||
*
|
||||
* @return array
|
||||
* @dataProvider table_names_provider
|
||||
*/
|
||||
public function table_names_provider() : array {
|
||||
return [
|
||||
[
|
||||
"SELECT *
|
||||
FROM {user} u
|
||||
JOIN (
|
||||
SELECT DISTINCT u.id FROM {user} u
|
||||
JOIN {user_enrolments} ue1 ON ue1.userid = u.id
|
||||
JOIN {enrol} e ON e.id = ue1.enrolid
|
||||
WHERE u.id NOT IN (
|
||||
SELECT DISTINCT ue.userid FROM {user_enrolments} ue
|
||||
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = 1)
|
||||
WHERE ue.status = 'active'
|
||||
AND e.status = 'enabled'
|
||||
AND ue.timestart < now()
|
||||
AND (ue.timeend = 0 OR ue.timeend > now())
|
||||
)
|
||||
) je ON je.id = u.id
|
||||
JOIN (
|
||||
SELECT DISTINCT ra.userid
|
||||
FROM {role_assignments} ra
|
||||
WHERE ra.roleid IN (1, 2, 3)
|
||||
AND ra.contextid = 'ctx'
|
||||
) rainner ON rainner.userid = u.id
|
||||
WHERE u.deleted = 0",
|
||||
[
|
||||
'user',
|
||||
'user',
|
||||
'user_enrolments',
|
||||
'enrol',
|
||||
'user_enrolments',
|
||||
'enrol',
|
||||
'role_assignments',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test moodle_read_slave_trait::table_names() query parser.
|
||||
*
|
||||
* @param string $sql
|
||||
* @param array $tables
|
||||
* @return void
|
||||
* @dataProvider table_names_provider
|
||||
*/
|
||||
public function test_table_names($sql, $tables) : void {
|
||||
$db = new read_slave_moodle_database_table_names();
|
||||
|
||||
$this->assertEquals($tables, $db->table_names($db->fix_sql_params($sql)[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test correct database handles are used in a read-read-write-read scenario.
|
||||
* Test lazy creation of the write handle.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_read_write_read() : void {
|
||||
$DB = $this->new_db(true);
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assert_readonly_handle($handle);
|
||||
$readsslave = $DB->perf_get_reads_slave();
|
||||
$this->assertGreaterThan(0, $readsslave);
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('table2');
|
||||
$this->assert_readonly_handle($handle);
|
||||
$readsslave = $DB->perf_get_reads_slave();
|
||||
$this->assertGreaterThan(1, $readsslave);
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$now = microtime(true);
|
||||
$handle = $DB->insert_record_raw('table', array('name' => 'blah'));
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
|
||||
if (microtime(true) - $now < self::$dbreadonlylatency) {
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals($readsslave, $DB->perf_get_reads_slave());
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assert_readonly_handle($handle);
|
||||
$this->assertEquals($readsslave + 1, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test correct database handles are used in a read-write-write scenario.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_write_write() : void {
|
||||
$DB = $this->new_db();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assert_readonly_handle($handle);
|
||||
$readsslave = $DB->perf_get_reads_slave();
|
||||
$this->assertGreaterThan(0, $readsslave);
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->insert_record_raw('table', array('name' => 'blah'));
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
|
||||
$handle = $DB->update_record_raw('table', array('id' => 1, 'name' => 'blah2'));
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals($readsslave, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test correct database handles are used in a write-read-read scenario.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_write_read_read() : void {
|
||||
$DB = $this->new_db();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->insert_record_raw('table', array('name' => 'blah'));
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
sleep(1);
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
$handle = $DB->get_records('table2');
|
||||
$this->assert_readonly_handle($handle);
|
||||
$this->assertEquals(1, $DB->perf_get_reads_slave());
|
||||
|
||||
$handle = $DB->get_records_sql("SELECT * FROM {table2} JOIN {table}");
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals(1, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly handle is not used for reading from temptables.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_temptable() : void {
|
||||
$DB = $this->new_db();
|
||||
$DB->add_temptable('temptable1');
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('temptable1');
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
|
||||
$DB->delete_temptable('temptable1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly handle is not used for reading from excluded tables.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_excluded_tables() : void {
|
||||
$DB = $this->new_db();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('exclude');
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly handle is not used during transactions.
|
||||
* Test last written time is adjusted post-transaction,
|
||||
* so the latency parameter is applied properly.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_transaction() : void {
|
||||
$DB = $this->new_db(true);
|
||||
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
$now = microtime(true);
|
||||
$handle = $DB->get_records_sql("SELECT * FROM {table}");
|
||||
// Use rw handle during transaction.
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
|
||||
$handle = $DB->insert_record_raw('table', array('name' => 'blah'));
|
||||
// Introduce delay so we can check that table write timestamps
|
||||
// are adjusted properly.
|
||||
sleep(1);
|
||||
$transaction->allow_commit();
|
||||
// This condition should always evaluate true, however we need to
|
||||
// safeguard from an unaccounted delay that can break this test.
|
||||
if (microtime(true) - $now < 1 + self::$dbreadonlylatency) {
|
||||
// Not enough time passed, use rw handle.
|
||||
$handle = $DB->get_records_sql("SELECT * FROM {table}");
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
|
||||
// Make sure enough time passes.
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
// Exceeded latency time, use ro handle.
|
||||
$handle = $DB->get_records_sql("SELECT * FROM {table}");
|
||||
$this->assert_readonly_handle($handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test failed readonly connection falls back to write connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_only_conn_fail() : void {
|
||||
$DB = $this->new_db(false, 'test_ro_fail');
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNotNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assertEquals('test_rw::test:test', $handle);
|
||||
$readsslave = $DB->perf_get_reads_slave();
|
||||
$this->assertEquals(0, $readsslave);
|
||||
}
|
||||
|
||||
/**
|
||||
* In multiple slaves scenario, test failed readonly connection falls back to
|
||||
* another readonly connection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_only_conn_first_fail() : void {
|
||||
$DB = $this->new_db(false, ['test_ro_fail', 'test_ro_ok']);
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$handle = $DB->get_records('table');
|
||||
$this->assertEquals('test_ro_ok::test:test', $handle);
|
||||
$readsslave = $DB->perf_get_reads_slave();
|
||||
$this->assertEquals(1, $readsslave);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to restore global $DB
|
||||
*
|
||||
* @param callable $test
|
||||
* @return void
|
||||
*/
|
||||
private function with_global_db($test) {
|
||||
global $DB;
|
||||
|
||||
$dbsave = $DB;
|
||||
try {
|
||||
$test();
|
||||
}
|
||||
finally {
|
||||
$DB = $dbsave;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lock_db table exclusion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_lock_db() : void {
|
||||
$this->with_global_db(function () {
|
||||
global $DB;
|
||||
|
||||
$DB = $this->new_db(true, ['test_ro'], read_slave_moodle_database_special::class);
|
||||
$DB->set_tables([
|
||||
'lock_db' => [
|
||||
'columns' => [
|
||||
'resourcekey' => (object)['meta_type' => ''],
|
||||
'owner' => (object)['meta_type' => ''],
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$lockfactory = new \core\lock\db_record_lock_factory('default');
|
||||
if (!$lockfactory->is_available()) {
|
||||
$this->markTestSkipped("db_record_lock_factory not available");
|
||||
}
|
||||
|
||||
$lock = $lockfactory->get_lock('abc', 2);
|
||||
$lock->release();
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertTrue($DB->perf_get_reads() > 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test sessions table exclusion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_sessions() : void {
|
||||
$this->with_global_db(function () {
|
||||
global $DB, $CFG;
|
||||
|
||||
$CFG->dbsessions = true;
|
||||
$DB = $this->new_db(true, ['test_ro'], read_slave_moodle_database_special::class);
|
||||
$DB->set_tables([
|
||||
'sessions' => [
|
||||
'columns' => [
|
||||
'sid' => (object)['meta_type' => ''],
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertNull($DB->get_dbhwrite());
|
||||
|
||||
$session = new \core\session\database();
|
||||
$session->handler_read('dummy');
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
$this->assertTrue($DB->perf_get_reads() > 0);
|
||||
});
|
||||
|
||||
\core\session\manager::restart_with_write_lock();
|
||||
}
|
||||
}
|
|
@ -5140,6 +5140,16 @@ class core_dml_testcase extends database_driver_testcase {
|
|||
if (!isset($cfg->dboptions)) {
|
||||
$cfg->dboptions = array();
|
||||
}
|
||||
// If we have a readonly slave situation, we need to either observe
|
||||
// the latency, or if the latency is not specified we need to take
|
||||
// the slave out because the table may not have propagated yet.
|
||||
if (isset($cfg->dboptions['readonly'])) {
|
||||
if (isset($cfg->dboptions['readonly']['latency'])) {
|
||||
usleep(intval(1000000 * $cfg->dboptions['readonly']['latency']));
|
||||
} else {
|
||||
unset($cfg->dboptions['readonly']);
|
||||
}
|
||||
}
|
||||
$DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
|
||||
$DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
|
||||
|
||||
|
|
232
lib/dml/tests/fixtures/read_slave_moodle_database.php
vendored
Normal file
232
lib/dml/tests/fixtures/read_slave_moodle_database.php
vendored
Normal file
|
@ -0,0 +1,232 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Database driver test class for testing moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/test_moodle_database.php');
|
||||
require_once(__DIR__.'/../../moodle_read_slave_trait.php');
|
||||
|
||||
/**
|
||||
* Database driver test class with moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class read_slave_moodle_database extends test_moodle_database {
|
||||
use moodle_read_slave_trait;
|
||||
|
||||
/** @var string */
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* Does not connect to the database. Sets handle property to $dbhost
|
||||
* @param string $dbhost
|
||||
* @param string $dbuser
|
||||
* @param string $dbpass
|
||||
* @param string $dbname
|
||||
* @param mixed $prefix
|
||||
* @param array $dboptions
|
||||
* @return bool true
|
||||
*/
|
||||
public function raw_connect(string $dbhost, string $dbuser, string $dbpass, string $dbname, $prefix, array $dboptions = null): bool {
|
||||
$dbport = isset($dboptions['dbport']) ? $dboptions['dbport'] : "";
|
||||
$this->handle = implode(':', [$dbhost, $dbport, $dbuser, $dbpass]);
|
||||
$this->prefix = $prefix;
|
||||
|
||||
if ($dbhost == 'test_ro_fail') {
|
||||
throw new dml_connection_exception($dbhost);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin database transaction
|
||||
* @return void
|
||||
*/
|
||||
protected function begin_transaction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit database transaction
|
||||
* @return void
|
||||
*/
|
||||
protected function commit_transaction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort database transaction
|
||||
* @return void
|
||||
*/
|
||||
protected function rollback_transaction() {
|
||||
$this->txnhandle = $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query wrapper that calls query_start() and query_end()
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param int $querytype
|
||||
* @return string $handle handle property
|
||||
*/
|
||||
private function with_query_start_end($sql, array $params = null, $querytype) {
|
||||
$this->query_start($sql, $params, $querytype);
|
||||
$ret = $this->handle;
|
||||
$this->query_end(null);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_dbhwrite()
|
||||
* @return string $dbhwrite handle property
|
||||
*/
|
||||
public function get_dbhwrite() {
|
||||
return $this->dbhwrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls with_query_start_end()
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function execute($sql, array $params = null) {
|
||||
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
|
||||
return $this->with_query_start_end($sql, $params, SQL_QUERY_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* get_records_sql() override, calls with_query_start_end()
|
||||
* @param string $sql the SQL select query to execute.
|
||||
* @param array $params array of sql parameters
|
||||
* @param int $limitfrom return a subset of records, starting at this point (optional).
|
||||
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
|
||||
* @return string $handle handle property
|
||||
*/
|
||||
public function get_records_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
|
||||
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
|
||||
return $this->with_query_start_end($sql, $params, SQL_QUERY_SELECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls with_query_start_end()
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param int $limitfrom
|
||||
* @param int $limitnum
|
||||
* @return bool true
|
||||
*/
|
||||
public function get_recordset_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
|
||||
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
|
||||
return $this->with_query_start_end($sql, $params, SQL_QUERY_SELECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls with_query_start_end()
|
||||
* @param string $table
|
||||
* @param array $params
|
||||
* @param bool $returnid
|
||||
* @param bool $bulk
|
||||
* @param bool $customsequence
|
||||
* @return string $handle handle property
|
||||
*/
|
||||
public function insert_record_raw($table, $params, $returnid = true, $bulk = false, $customsequence = false) {
|
||||
$fields = implode(',', array_keys($params));
|
||||
$i = 1;
|
||||
foreach ($params as $value) {
|
||||
$values[] = "\$".$i++;
|
||||
}
|
||||
$values = implode(',', $values);
|
||||
$sql = "INSERT INTO {$this->prefix}$table ($fields) VALUES($values)";
|
||||
return $this->with_query_start_end($sql, $params, SQL_QUERY_INSERT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls with_query_start_end()
|
||||
* @param string $table
|
||||
* @param array $params
|
||||
* @param bool $bulk
|
||||
* @return string $handle handle property
|
||||
*/
|
||||
public function update_record_raw($table, $params, $bulk = false) {
|
||||
$id = $params['id'];
|
||||
unset($params['id']);
|
||||
$i = 1;
|
||||
$sets = array();
|
||||
foreach ($params as $field => $value) {
|
||||
$sets[] = "$field = \$".$i++;
|
||||
}
|
||||
$params[] = $id;
|
||||
$sets = implode(',', $sets);
|
||||
$sql = "UPDATE {$this->prefix}$table SET $sets WHERE id=\$".$i;
|
||||
return $this->with_query_start_end($sql, $params, SQL_QUERY_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets handle property
|
||||
* @return string $handle handle property
|
||||
*/
|
||||
protected function get_db_handle() {
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets handle property
|
||||
* @param string $dbh
|
||||
* @return void
|
||||
*/
|
||||
protected function set_db_handle($dbh) {
|
||||
$this->handle = $dbh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add temptable
|
||||
* @param string $temptable
|
||||
* @return void
|
||||
*/
|
||||
public function add_temptable($temptable) {
|
||||
$this->temptables->add_temptable($temptable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove temptable
|
||||
* @param string $temptable
|
||||
* @return void
|
||||
*/
|
||||
public function delete_temptable($temptable) {
|
||||
$this->temptables->delete_temptable($temptable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is session lock supported in this driver?
|
||||
* @return bool
|
||||
*/
|
||||
public function session_lock_supported() {
|
||||
return true;
|
||||
}
|
||||
}
|
64
lib/dml/tests/fixtures/read_slave_moodle_database_mock_mysqli.php
vendored
Normal file
64
lib/dml/tests/fixtures/read_slave_moodle_database_mock_mysqli.php
vendored
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Database driver test class for testing mysqli_native_moodle_database with moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/../../mysqli_native_moodle_database.php');
|
||||
require_once(__DIR__.'/test_moodle_read_slave_trait.php');
|
||||
|
||||
/**
|
||||
* Database driver mock test class that exposes some methods
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class read_slave_moodle_database_mock_mysqli extends mysqli_native_moodle_database {
|
||||
use test_moodle_read_slave_trait;
|
||||
|
||||
/**
|
||||
* Return tables in database WITHOUT current prefix
|
||||
* @param bool $usecache if true, returns list of cached tables.
|
||||
* @return array of table names in lowercase and without prefix
|
||||
*/
|
||||
public function get_tables($usecache = true) {
|
||||
if ($this->tables === null) {
|
||||
$this->tables = [];
|
||||
}
|
||||
return $this->tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used by database_manager
|
||||
* @param string|array $sql query
|
||||
* @param array|null $tablenames an array of xmldb table names affected by this request.
|
||||
* @return bool true
|
||||
* @throws ddl_change_structure_exception A DDL specific exception is thrown for any errors.
|
||||
*/
|
||||
public function change_database_structure($sql, $tablenames = null) {
|
||||
return true;
|
||||
}
|
||||
}
|
41
lib/dml/tests/fixtures/read_slave_moodle_database_mock_pgsql.php
vendored
Normal file
41
lib/dml/tests/fixtures/read_slave_moodle_database_mock_pgsql.php
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Database driver test class for testing pgsql_native_moodle_database with moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/../../pgsql_native_moodle_database.php');
|
||||
require_once(__DIR__.'/test_moodle_read_slave_trait.php');
|
||||
|
||||
/**
|
||||
* Database driver mock test class that exposes some methods
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class read_slave_moodle_database_mock_pgsql extends pgsql_native_moodle_database {
|
||||
use test_moodle_read_slave_trait;
|
||||
}
|
76
lib/dml/tests/fixtures/read_slave_moodle_database_special.php
vendored
Normal file
76
lib/dml/tests/fixtures/read_slave_moodle_database_special.php
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Database driver test class for testing moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/read_slave_moodle_database.php');
|
||||
require_once(__DIR__.'/read_slave_moodle_recordset_special.php');
|
||||
|
||||
/**
|
||||
* Database driver mock test class that uses read_slave_moodle_recordset_special
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class read_slave_moodle_database_special extends read_slave_moodle_database {
|
||||
/**
|
||||
* Returns empty array
|
||||
* @param string $sql the SQL select query to execute.
|
||||
* @param array $params array of sql parameters
|
||||
* @param int $limitfrom return a subset of records, starting at this point (optional).
|
||||
* @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
|
||||
* @return string $handle handle property
|
||||
*/
|
||||
public function get_records_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
|
||||
$dbhandle = parent::get_records_sql($sql, $params);
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fake recordset
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param int $limitfrom
|
||||
* @param int $limitnum
|
||||
* @return bool true
|
||||
*/
|
||||
public function get_recordset_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
|
||||
$dbhandle = parent::get_recordset_sql($sql, $params);
|
||||
return new read_slave_moodle_recordset_special();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the records in a table where all the given conditions met.
|
||||
*
|
||||
* @param string $table The table to query.
|
||||
* @param array $conditions optional array $fieldname=>requestedvalue with AND in between
|
||||
* @return int The count of records returned from the specified criteria.
|
||||
*/
|
||||
public function count_records($table, array $conditions = null) {
|
||||
return 1;
|
||||
}
|
||||
}
|
52
lib/dml/tests/fixtures/read_slave_moodle_database_table_names.php
vendored
Normal file
52
lib/dml/tests/fixtures/read_slave_moodle_database_table_names.php
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Database driver test class for testing moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/read_slave_moodle_database.php');
|
||||
|
||||
/**
|
||||
* Database driver test class that exposes table_names()
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class read_slave_moodle_database_table_names extends read_slave_moodle_database {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = 't_';
|
||||
|
||||
/**
|
||||
* Upgrade to public
|
||||
* @param string $sql
|
||||
* @return array
|
||||
*/
|
||||
public function table_names(string $sql) : array {
|
||||
return parent::table_names($sql);
|
||||
}
|
||||
}
|
69
lib/dml/tests/fixtures/read_slave_moodle_recordset_special.php
vendored
Normal file
69
lib/dml/tests/fixtures/read_slave_moodle_recordset_special.php
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Database driver test class for testing moodle_read_slave_trait
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Database recordset mock test class
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class read_slave_moodle_recordset_special extends moodle_recordset {
|
||||
/**
|
||||
* Iterator interface
|
||||
* @return void
|
||||
*/
|
||||
public function close() {
|
||||
}
|
||||
/**
|
||||
* Iterator interface
|
||||
* @return stdClass
|
||||
*/
|
||||
public function current() {
|
||||
return new stdClass();
|
||||
}
|
||||
/**
|
||||
* Iterator interface
|
||||
* @return void
|
||||
*/
|
||||
public function next() {
|
||||
}
|
||||
/**
|
||||
* Iterator interface
|
||||
* @return mixed
|
||||
*/
|
||||
public function key() {
|
||||
}
|
||||
/**
|
||||
* Iterator interface
|
||||
* @return bool
|
||||
*/
|
||||
public function valid() {
|
||||
return false;
|
||||
}
|
||||
}
|
384
lib/dml/tests/fixtures/test_moodle_database.php
vendored
Normal file
384
lib/dml/tests/fixtures/test_moodle_database.php
vendored
Normal file
|
@ -0,0 +1,384 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Abstract database driver test class providing some moodle database interface
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/../../moodle_database.php');
|
||||
require_once(__DIR__.'/../../moodle_temptables.php');
|
||||
require_once(__DIR__.'/../../../ddl/database_manager.php');
|
||||
require_once(__DIR__.'/test_sql_generator.php');
|
||||
|
||||
/**
|
||||
* Abstract database driver test class
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class test_moodle_database extends moodle_database {
|
||||
|
||||
/** @var string */
|
||||
private $error;
|
||||
|
||||
/** @var array */
|
||||
private $_tables = [];
|
||||
|
||||
/**
|
||||
* Constructor - Instantiates the database
|
||||
* @param bool $external True means that an external database is used.
|
||||
*/
|
||||
public function __construct($external = false) {
|
||||
parent::__construct($external);
|
||||
|
||||
$this->temptables = new moodle_temptables($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return boolean true
|
||||
*/
|
||||
public function driver_installed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return string 'test'
|
||||
*/
|
||||
public function get_dbfamily() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return string 'test'
|
||||
*/
|
||||
protected function get_dbtype() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return string 'test'
|
||||
*/
|
||||
protected function get_dblibrary() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return string 'test'
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'test';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return string
|
||||
*/
|
||||
public function get_configuration_help() {
|
||||
return 'test database driver';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return array
|
||||
*/
|
||||
public function get_server_info() {
|
||||
return ['description' => $this->name(), 'version' => '0'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @return int 0
|
||||
*/
|
||||
protected function allowed_param_types() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns error property
|
||||
* @return string $error
|
||||
*/
|
||||
public function get_last_error() {
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tables property
|
||||
* @param array $tables
|
||||
* @return void
|
||||
*/
|
||||
public function set_tables($tables) {
|
||||
$this->_tables = $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns keys of tables property
|
||||
* @param bool $usecache
|
||||
* @return array $tablenames
|
||||
*/
|
||||
public function get_tables($usecache = true) {
|
||||
return array_keys($this->_tables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return table indexes
|
||||
* @param string $table
|
||||
* @return array $indexes
|
||||
*/
|
||||
public function get_indexes($table) {
|
||||
return isset($this->_tables[$table]['indexes']) ? $this->_tables[$table]['indexes'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return table columns
|
||||
* @param string $table
|
||||
* @return array database_column_info[] of database_column_info objects indexed with column names
|
||||
*/
|
||||
public function fetch_columns($table) : array {
|
||||
return $this->_tables[$table]['columns'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @param StdClass $column metadata
|
||||
* @param mixed $value
|
||||
* @return mixed $value
|
||||
*/
|
||||
protected function normalise_value($column, $value) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation
|
||||
* @param string|array $sql
|
||||
* @param array|null $tablenames
|
||||
* @return bool true
|
||||
*/
|
||||
public function change_database_structure($sql, $tablenames = null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function execute($sql, array $params = null) {
|
||||
throw new Exception("execute() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param int $limitfrom
|
||||
* @param int $limitnum
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_recordset_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
|
||||
throw new Exception("get_recordset_sql() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param int $limitfrom
|
||||
* @param int $limitnum
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_records_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
|
||||
throw new Exception("get_records_sql() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_fieldset_sql($sql, array $params = null) {
|
||||
throw new Exception("get_fieldset_sql() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param array $params
|
||||
* @param bool $returnid
|
||||
* @param bool $bulk
|
||||
* @param bool $customsequence
|
||||
* @return bool|int true or new id
|
||||
* @throws Exception
|
||||
*/
|
||||
public function insert_record_raw($table, $params, $returnid = true, $bulk = false, $customsequence = false) {
|
||||
throw new Exception("insert_record_raw() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param StdObject $dataobject
|
||||
* @param bool $returnid
|
||||
* @param bool $bulk
|
||||
* @return bool|int true or new id
|
||||
* @throws Exception
|
||||
*/
|
||||
public function insert_record($table, $dataobject, $returnid = true, $bulk = false) {
|
||||
return $this->insert_record_raw($table, (array)$dataobject, $returnid, $bulk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param StdObject $dataobject
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function import_record($table, $dataobject) {
|
||||
throw new Exception("import_record() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param array $params
|
||||
* @param bool $bulk
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update_record_raw($table, $params, $bulk = false) {
|
||||
throw new Exception("update_record_raw() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param StdObject $dataobject
|
||||
* @param bool $bulk
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update_record($table, $dataobject, $bulk = false) {
|
||||
throw new Exception("update_record() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param string $newfield
|
||||
* @param string $newvalue
|
||||
* @param string $select
|
||||
* @param array $params
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function set_field_select($table, $newfield, $newvalue, $select, array $params = null) {
|
||||
throw new Exception("set_field_select() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $table
|
||||
* @param string $select
|
||||
* @param array $params
|
||||
* @return bool true
|
||||
* @throws Exception
|
||||
*/
|
||||
public function delete_records_select($table, $select, array $params = null) {
|
||||
throw new Exception("delete_records_select() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @return string $sql
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sql_concat() {
|
||||
throw new Exception("sql_concat() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @param string $separator
|
||||
* @param array $elements
|
||||
* @return string $sql
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sql_concat_join($separator = "' '", $elements = []) {
|
||||
throw new Exception("sql_concat_join() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function begin_transaction() {
|
||||
throw new Exception("begin_transaction() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function commit_transaction() {
|
||||
throw new Exception("commit_transaction() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation, throws Exception
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function rollback_transaction() {
|
||||
throw new Exception("rollback_transaction() not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database manager used for db manipulation.
|
||||
* Used mostly in upgrade.php scripts.
|
||||
* @return database_manager The instance used to perform ddl operations.
|
||||
* @see lib/ddl/database_manager.php
|
||||
*/
|
||||
public function get_manager() {
|
||||
if (!$this->database_manager) {
|
||||
$generator = new test_sql_generator($this, $this->temptables);
|
||||
|
||||
$this->database_manager = new database_manager($this, $generator);
|
||||
}
|
||||
return $this->database_manager;
|
||||
}
|
||||
}
|
89
lib/dml/tests/fixtures/test_moodle_read_slave_trait.php
vendored
Normal file
89
lib/dml/tests/fixtures/test_moodle_read_slave_trait.php
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Read slave helper that exposes selected moodle_read_slave_trait metods
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/../../pgsql_native_moodle_database.php');
|
||||
|
||||
/**
|
||||
* Read slave helper that exposes selected moodle_read_slave_trait metods
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
trait test_moodle_read_slave_trait {
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Constructs a mock db driver
|
||||
*
|
||||
* @param bool $external
|
||||
*/
|
||||
public function __construct($external = false) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
parent::__construct($external);
|
||||
|
||||
$this->wantreadslave = true;
|
||||
$this->dbhwrite = 'test_rw';
|
||||
$this->dbhreadonly = 'test_ro';
|
||||
$this->set_db_handle($this->dbhwrite);
|
||||
|
||||
$this->temptables = new moodle_temptables($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade to public
|
||||
* @return resource
|
||||
*/
|
||||
public function get_db_handle() {
|
||||
return parent::get_db_handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade to public
|
||||
* @param string $sql
|
||||
* @param array $params
|
||||
* @param int $type
|
||||
* @param array $extrainfo
|
||||
*/
|
||||
public function query_start($sql, array $params = null, $type, $extrainfo = null) {
|
||||
return parent::query_start($sql, $params, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade to public
|
||||
* @param mixed $result
|
||||
*/
|
||||
public function query_end($result) {
|
||||
$this->set_db_handle($this->dbhwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade to public
|
||||
*/
|
||||
public function dispose() {
|
||||
}
|
||||
}
|
128
lib/dml/tests/fixtures/test_sql_generator.php
vendored
Normal file
128
lib/dml/tests/fixtures/test_sql_generator.php
vendored
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Test SQL code generator class
|
||||
*
|
||||
* @package core
|
||||
* @category dml
|
||||
* @copyright 2018 Srdjan Janković, Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/../../../ddl/sql_generator.php');
|
||||
|
||||
/**
|
||||
* Test SQL code generator class
|
||||
*
|
||||
* @package core
|
||||
* @category ddl
|
||||
* @copyright 2018 Catalyst IT
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class test_sql_generator extends sql_generator {
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Reset a sequence to the id field of a table.
|
||||
*
|
||||
* @param xmldb_table|string $table name of table or the table object.
|
||||
* @return array of sql statements
|
||||
*/
|
||||
public function getResetSequenceSQL($table) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return [];
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Given one correct xmldb_table, returns the SQL statements
|
||||
* to create temporary table (inside one array).
|
||||
*
|
||||
* @param xmldb_table $xmldbtable The xmldb_table object instance.
|
||||
* @return array of sql statements
|
||||
*/
|
||||
public function getCreateTempTableSQL($xmldbtable) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return [];
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Given one XMLDB Type, length and decimals, returns the DB proper SQL type.
|
||||
*
|
||||
* @param int $xmldbtype The xmldb_type defined constant. XMLDB_TYPE_INTEGER and other XMLDB_TYPE_* constants.
|
||||
* @param int $xmldblength The length of that data type.
|
||||
* @param int $xmldbdecimals The decimal places of precision of the data type.
|
||||
* @return string The DB defined data type.
|
||||
*/
|
||||
public function getTypeSQL($xmldbtype, $xmldblength = null, $xmldbdecimals = null) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return '';
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Returns the code (array of statements) needed to add one comment to the table.
|
||||
*
|
||||
* @param xmldb_table $xmldbtable The xmldb_table object instance.
|
||||
* @return array Array of SQL statements to add one comment to the table.
|
||||
*/
|
||||
function getCommentSQL ($xmldbtable) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return [];
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default
|
||||
* (usually invoked from getModifyDefaultSQL()
|
||||
*
|
||||
* @param xmldb_table $xmldbtable The xmldb_table object instance.
|
||||
* @param xmldb_field $xmldbfield The xmldb_field object instance.
|
||||
* @return array Array of SQL statements to create a field's default.
|
||||
*/
|
||||
public function getCreateDefaultSQL($xmldbtable, $xmldbfield) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return [];
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default
|
||||
* (usually invoked from getModifyDefaultSQL()
|
||||
*
|
||||
* @param xmldb_table $xmldbtable The xmldb_table object instance.
|
||||
* @param xmldb_field $xmldbfield The xmldb_field object instance.
|
||||
* @return array Array of SQL statements to create a field's default.
|
||||
*/
|
||||
public function getDropDefaultSQL($xmldbtable, $xmldbfield) {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return [];
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
/**
|
||||
* Returns an array of reserved words (lowercase) for this DB
|
||||
* @return array An array of database specific reserved words
|
||||
*/
|
||||
public static function getReservedWords() {
|
||||
// @codingStandardsIgnoreEnd
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue