mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 08:26: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
|
@ -79,6 +79,45 @@ $CFG->dboptions = array(
|
||||||
// set to zero if you are using pg_bouncer in
|
// set to zero if you are using pg_bouncer in
|
||||||
// 'transaction' mode (it is fine in 'session'
|
// 'transaction' mode (it is fine in 'session'
|
||||||
// mode).
|
// mode).
|
||||||
|
/*
|
||||||
|
'connecttimeout' => null, // Set connect timeout in seconds. Not all drivers support it.
|
||||||
|
'readonly' => [ // Set to read-only slave details, to get safe reads
|
||||||
|
// from there instead of the master node. Optional.
|
||||||
|
// Currently supported by pgsql and mysqli variety classes.
|
||||||
|
// If not supported silently ignored.
|
||||||
|
'instance' => [ // Readonly slave connection parameters
|
||||||
|
[
|
||||||
|
'dbhost' => 'slave.dbhost',
|
||||||
|
'dbport' => '', // Defaults to master port
|
||||||
|
'dbuser' => '', // Defaults to master user
|
||||||
|
'dbpass' => '', // Defaults to master password
|
||||||
|
],
|
||||||
|
[...],
|
||||||
|
],
|
||||||
|
|
||||||
|
Instance(s) can alternatively be specified as:
|
||||||
|
|
||||||
|
'instance' => 'slave.dbhost',
|
||||||
|
'instance' => ['slave.dbhost1', 'slave.dbhost2'],
|
||||||
|
'instance' => ['dbhost' => 'slave.dbhost', 'dbport' => '', 'dbuser' => '', 'dbpass' => ''],
|
||||||
|
|
||||||
|
'connecttimeout' => 2, // Set read-only slave connect timeout in seconds. See above.
|
||||||
|
'latency' => 0.5, // Set read-only slave sync latency in seconds.
|
||||||
|
// When 'latency' seconds have lapsed after an update to a table
|
||||||
|
// it is deemed safe to use readonly slave for reading from the table.
|
||||||
|
// It is optional. If omitted once written to a table it will always
|
||||||
|
// use master handle for reading.
|
||||||
|
// Lower values increase the performance, but setting it too low means
|
||||||
|
// missing the master-slave sync.
|
||||||
|
'exclude_tables' => [ // Tables to exclude from read-only slave feature.
|
||||||
|
'table1', // Should not be used, unless in rare cases when some area of the system
|
||||||
|
'table2', // is malfunctioning and you still want to use readonly feature.
|
||||||
|
], // Then one can exclude offending tables while investigating.
|
||||||
|
|
||||||
|
More info available in lib/dml/moodle_read_slave_trait.php where the feature is implemented.
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
// For all database config settings see https://docs.moodle.org/en/Database_settings
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -108,13 +108,13 @@ abstract class moodle_database {
|
||||||
/** @var float Last time in seconds with millisecond precision. */
|
/** @var float Last time in seconds with millisecond precision. */
|
||||||
protected $last_time;
|
protected $last_time;
|
||||||
/** @var bool Flag indicating logging of query in progress. This helps prevent infinite loops. */
|
/** @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. */
|
/** @var bool True if the db is used for db sessions. */
|
||||||
protected $used_for_db_sessions = false;
|
protected $used_for_db_sessions = false;
|
||||||
|
|
||||||
/** @var array Array containing open transactions. */
|
/** @var array Array containing open transactions. */
|
||||||
private $transactions = array();
|
protected $transactions = array();
|
||||||
/** @var bool Flag used to force rollback of all current transactions. */
|
/** @var bool Flag used to force rollback of all current transactions. */
|
||||||
private $force_rollback = false;
|
private $force_rollback = false;
|
||||||
|
|
||||||
|
@ -2717,6 +2717,22 @@ abstract class moodle_database {
|
||||||
return $this->reads;
|
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.
|
* Returns the number of writes done by this database.
|
||||||
* @return int Number of writes.
|
* @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();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
require_once(__DIR__.'/moodle_database.php');
|
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_recordset.php');
|
||||||
require_once(__DIR__.'/mysqli_native_moodle_temptables.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
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
class mysqli_native_moodle_database extends moodle_database {
|
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 */
|
/** @var mysqli $mysqli */
|
||||||
protected $mysqli = null;
|
protected $mysqli = null;
|
||||||
|
@ -235,6 +239,14 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
if (isset($this->dboptions['dbcollation'])) {
|
if (isset($this->dboptions['dbcollation'])) {
|
||||||
return $this->dboptions['dbcollation'];
|
return $this->dboptions['dbcollation'];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set 'dbcollation' option
|
||||||
|
*
|
||||||
|
* @return string $dbcollation
|
||||||
|
*/
|
||||||
|
private function detect_collation(): string {
|
||||||
if ($this->external) {
|
if ($this->external) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -246,9 +258,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
$sql = "SELECT collation_name
|
$sql = "SELECT collation_name
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config' AND column_name = 'value'";
|
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);
|
$result = $this->mysqli->query($sql);
|
||||||
$this->query_end($result);
|
|
||||||
if ($rec = $result->fetch_assoc()) {
|
if ($rec = $result->fetch_assoc()) {
|
||||||
// MySQL 8 BC: information_schema.* returns the fields in upper case.
|
// MySQL 8 BC: information_schema.* returns the fields in upper case.
|
||||||
$rec = array_change_key_case($rec, CASE_LOWER);
|
$rec = array_change_key_case($rec, CASE_LOWER);
|
||||||
|
@ -260,9 +270,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
if (!$collation) {
|
if (!$collation) {
|
||||||
// Get the default database collation, but only if using UTF-8.
|
// Get the default database collation, but only if using UTF-8.
|
||||||
$sql = "SELECT @@collation_database";
|
$sql = "SELECT @@collation_database";
|
||||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
|
||||||
$result = $this->mysqli->query($sql);
|
$result = $this->mysqli->query($sql);
|
||||||
$this->query_end($result);
|
|
||||||
if ($rec = $result->fetch_assoc()) {
|
if ($rec = $result->fetch_assoc()) {
|
||||||
if (strpos($rec['@@collation_database'], 'utf8_') === 0 || strpos($rec['@@collation_database'], 'utf8mb4_') === 0) {
|
if (strpos($rec['@@collation_database'], 'utf8_') === 0 || strpos($rec['@@collation_database'], 'utf8mb4_') === 0) {
|
||||||
$collation = $rec['@@collation_database'];
|
$collation = $rec['@@collation_database'];
|
||||||
|
@ -275,9 +283,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
// We want only utf8 compatible collations.
|
// We want only utf8 compatible collations.
|
||||||
$collation = null;
|
$collation = null;
|
||||||
$sql = "SHOW COLLATION WHERE Collation LIKE 'utf8mb4\_%' AND Charset = 'utf8mb4'";
|
$sql = "SHOW COLLATION WHERE Collation LIKE 'utf8mb4\_%' AND Charset = 'utf8mb4'";
|
||||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
|
||||||
$result = $this->mysqli->query($sql);
|
$result = $this->mysqli->query($sql);
|
||||||
$this->query_end($result);
|
|
||||||
while ($res = $result->fetch_assoc()) {
|
while ($res = $result->fetch_assoc()) {
|
||||||
$collation = $res['Collation'];
|
$collation = $res['Collation'];
|
||||||
if (strtoupper($res['Default']) === 'YES') {
|
if (strtoupper($res['Default']) === 'YES') {
|
||||||
|
@ -518,7 +524,6 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to db
|
* Connect to db
|
||||||
* Must be called before other methods.
|
|
||||||
* @param string $dbhost The database host.
|
* @param string $dbhost The database host.
|
||||||
* @param string $dbuser The database username.
|
* @param string $dbuser The database username.
|
||||||
* @param string $dbpass The database username's password.
|
* @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
|
* @param array $dboptions driver specific options
|
||||||
* @return bool success
|
* @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();
|
$driverstatus = $this->driver_installed();
|
||||||
|
|
||||||
if ($driverstatus !== true) {
|
if ($driverstatus !== true) {
|
||||||
|
@ -556,10 +561,19 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
if ($dbhost and !empty($this->dboptions['dbpersist'])) {
|
if ($dbhost and !empty($this->dboptions['dbpersist'])) {
|
||||||
$dbhost = "p:$dbhost";
|
$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) {
|
$conn = null;
|
||||||
$dberr = $this->mysqli->connect_error;
|
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;
|
$this->mysqli = null;
|
||||||
throw new dml_connection_exception($dberr);
|
throw new dml_connection_exception($dberr);
|
||||||
}
|
}
|
||||||
|
@ -568,16 +582,14 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||||
$this->query_log_prevent();
|
$this->query_log_prevent();
|
||||||
|
|
||||||
if (isset($dboptions['dbcollation'])) {
|
if (isset($dboptions['dbcollation'])) {
|
||||||
$collationinfo = explode('_', $dboptions['dbcollation']);
|
$collation = $this->dboptions['dbcollation'] = $dboptions['dbcollation'];
|
||||||
$this->dboptions['dbcollation'] = $dboptions['dbcollation'];
|
|
||||||
} else {
|
} else {
|
||||||
$collationinfo = explode('_', $this->get_dbcollation());
|
$collation = $this->detect_collation();
|
||||||
}
|
}
|
||||||
|
$collationinfo = explode('_', $collation);
|
||||||
$charset = reset($collationinfo);
|
$charset = reset($collationinfo);
|
||||||
|
|
||||||
$this->query_start("--set_charset()", null, SQL_QUERY_AUX);
|
|
||||||
$this->mysqli->set_charset($charset);
|
$this->mysqli->set_charset($charset);
|
||||||
$this->query_end(true);
|
|
||||||
|
|
||||||
// If available, enforce strict mode for the session. That guaranties
|
// If available, enforce strict mode for the session. That guaranties
|
||||||
// standard behaviour under some situations, avoiding some MySQL nasty
|
// 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();
|
$si = $this->get_server_info();
|
||||||
if (version_compare($si['version'], '5.0.2', '>=')) {
|
if (version_compare($si['version'], '5.0.2', '>=')) {
|
||||||
$sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES'";
|
$sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES'";
|
||||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
|
||||||
$result = $this->mysqli->query($sql);
|
$result = $this->mysqli->query($sql);
|
||||||
$this->query_end($result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can enable logging now.
|
// 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
|
* Returns database server info array
|
||||||
* @return array Array containing 'description' and 'version' info
|
* @return array Array containing 'description' and 'version' info
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
require_once(__DIR__.'/moodle_database.php');
|
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_recordset.php');
|
||||||
require_once(__DIR__.'/pgsql_native_moodle_temptables.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
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
class pgsql_native_moodle_database extends moodle_database {
|
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 */
|
/** @var resource $pgsql database resource */
|
||||||
protected $pgsql = null;
|
protected $pgsql = null;
|
||||||
|
@ -110,7 +119,6 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect to db
|
* Connect to db
|
||||||
* Must be called before other methods.
|
|
||||||
* @param string $dbhost The database host.
|
* @param string $dbhost The database host.
|
||||||
* @param string $dbuser The database username.
|
* @param string $dbuser The database username.
|
||||||
* @param string $dbpass The database username's password.
|
* @param string $dbpass The database username's password.
|
||||||
|
@ -120,7 +128,7 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||||
* @return bool true
|
* @return bool true
|
||||||
* @throws dml_connection_exception if error
|
* @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) {
|
if ($prefix == '' and !$this->external) {
|
||||||
//Enforce prefixes for everybody but mysql
|
//Enforce prefixes for everybody but mysql
|
||||||
throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
|
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'";
|
$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'])) {
|
if (empty($this->dboptions['dbhandlesoptions'])) {
|
||||||
// ALTER USER and ALTER DATABASE are overridden by these settings.
|
// ALTER USER and ALTER DATABASE are overridden by these settings.
|
||||||
$options = array('--client_encoding=utf8', '--standard_conforming_strings=on');
|
$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.
|
* Called before each db query.
|
||||||
|
@ -242,8 +312,8 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
|
protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
|
||||||
parent::query_start($sql, $params, $type, $extrainfo);
|
$this->read_slave_query_start($sql, $params, $type, $extrainfo);
|
||||||
// pgsql driver tents to send debug to output, we do not need that ;-)
|
// pgsql driver tends to send debug to output, we do not need that.
|
||||||
$this->last_error_reporting = error_reporting(0);
|
$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);
|
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
|
// 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).
|
// loading the entire thing (unless the config setting is turned off).
|
||||||
$usecursors = !$limitnum && ($this->get_fetch_buffer_size() > 0);
|
$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.
|
// Do the query to a cursor.
|
||||||
$sql = 'DECLARE ' . $cursorname . ' NO SCROLL CURSOR WITH HOLD FOR ' . $sql;
|
$sql = 'DECLARE ' . $cursorname . ' NO SCROLL CURSOR WITH HOLD FOR ' . $sql;
|
||||||
$result = pg_query_params($this->pgsql, $sql, $params);
|
|
||||||
} else {
|
} else {
|
||||||
$result = pg_query_params($this->pgsql, $sql, $params);
|
|
||||||
$cursorname = '';
|
$cursorname = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->query_start($sql, $params, SQL_QUERY_SELECT);
|
||||||
|
|
||||||
|
$result = pg_query_params($this->pgsql, $sql, $params);
|
||||||
|
|
||||||
$this->query_end($result);
|
$this->query_end($result);
|
||||||
if ($usecursors) {
|
if ($usecursors) {
|
||||||
pg_free_result($result);
|
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)) {
|
if (!isset($cfg->dboptions)) {
|
||||||
$cfg->dboptions = array();
|
$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 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
|
||||||
$DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
|
$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 [];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9523,6 +9523,12 @@ function get_performance_info() {
|
||||||
$info['html'] .= '<li class="dbqueries col-sm-4">DB reads/writes: '.$info['dbqueries'].'</li> ';
|
$info['html'] .= '<li class="dbqueries col-sm-4">DB reads/writes: '.$info['dbqueries'].'</li> ';
|
||||||
$info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
|
$info['txt'] .= 'db reads/writes: '.$info['dbqueries'].' ';
|
||||||
|
|
||||||
|
if ($DB->want_read_slave()) {
|
||||||
|
$info['dbreads_slave'] = $DB->perf_get_reads_slave();
|
||||||
|
$info['html'] .= '<li class="dbqueries col-sm-4">DB reads from slave: '.$info['dbreads_slave'].'</li> ';
|
||||||
|
$info['txt'] .= 'db reads from slave: '.$info['dbreads_slave'].' ';
|
||||||
|
}
|
||||||
|
|
||||||
$info['dbtime'] = round($DB->perf_get_queries_time(), 5);
|
$info['dbtime'] = round($DB->perf_get_queries_time(), 5);
|
||||||
$info['html'] .= '<li class="dbtime col-sm-4">DB queries time: '.$info['dbtime'].' secs</li> ';
|
$info['html'] .= '<li class="dbtime col-sm-4">DB queries time: '.$info['dbtime'].' secs</li> ';
|
||||||
$info['txt'] .= 'db queries time: ' . $info['dbtime'] . 's ';
|
$info['txt'] .= 'db queries time: ' . $info['dbtime'] . 's ';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue