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:
Srdjan 2020-05-07 14:14:29 +10:00
parent d85118369d
commit 46cfde3d95
19 changed files with 2422 additions and 28 deletions

View file

@ -108,13 +108,13 @@ abstract class moodle_database {
/** @var float Last time in seconds with millisecond precision. */
protected $last_time;
/** @var bool Flag indicating logging of query in progress. This helps prevent infinite loops. */
private $loggingquery = false;
protected $loggingquery = false;
/** @var bool True if the db is used for db sessions. */
protected $used_for_db_sessions = false;
/** @var array Array containing open transactions. */
private $transactions = array();
protected $transactions = array();
/** @var bool Flag used to force rollback of all current transactions. */
private $force_rollback = false;
@ -2717,6 +2717,22 @@ abstract class moodle_database {
return $this->reads;
}
/**
* Returns whether we want to connect to slave database for read queries.
* @return bool Want read only connection
*/
public function want_read_slave(): bool {
return false;
}
/**
* Returns the number of reads before first write done by this database.
* @return int Number of reads.
*/
public function perf_get_reads_slave(): int {
return 0;
}
/**
* Returns the number of writes done by this database.
* @return int Number of writes.

View 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];
}
}

View file

@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/moodle_database.php');
require_once(__DIR__.'/moodle_read_slave_trait.php');
require_once(__DIR__.'/mysqli_native_moodle_recordset.php');
require_once(__DIR__.'/mysqli_native_moodle_temptables.php');
@ -36,6 +37,9 @@ require_once(__DIR__.'/mysqli_native_moodle_temptables.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mysqli_native_moodle_database extends moodle_database {
use moodle_read_slave_trait {
can_use_readonly as read_slave_can_use_readonly;
}
/** @var mysqli $mysqli */
protected $mysqli = null;
@ -235,6 +239,14 @@ class mysqli_native_moodle_database extends moodle_database {
if (isset($this->dboptions['dbcollation'])) {
return $this->dboptions['dbcollation'];
}
}
/**
* Set 'dbcollation' option
*
* @return string $dbcollation
*/
private function detect_collation(): string {
if ($this->external) {
return null;
}
@ -246,9 +258,7 @@ class mysqli_native_moodle_database extends moodle_database {
$sql = "SELECT collation_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config' AND column_name = 'value'";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
if ($rec = $result->fetch_assoc()) {
// MySQL 8 BC: information_schema.* returns the fields in upper case.
$rec = array_change_key_case($rec, CASE_LOWER);
@ -260,9 +270,7 @@ class mysqli_native_moodle_database extends moodle_database {
if (!$collation) {
// Get the default database collation, but only if using UTF-8.
$sql = "SELECT @@collation_database";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
if ($rec = $result->fetch_assoc()) {
if (strpos($rec['@@collation_database'], 'utf8_') === 0 || strpos($rec['@@collation_database'], 'utf8mb4_') === 0) {
$collation = $rec['@@collation_database'];
@ -275,9 +283,7 @@ class mysqli_native_moodle_database extends moodle_database {
// We want only utf8 compatible collations.
$collation = null;
$sql = "SHOW COLLATION WHERE Collation LIKE 'utf8mb4\_%' AND Charset = 'utf8mb4'";
$this->query_start($sql, NULL, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
while ($res = $result->fetch_assoc()) {
$collation = $res['Collation'];
if (strtoupper($res['Default']) === 'YES') {
@ -518,7 +524,6 @@ class mysqli_native_moodle_database extends moodle_database {
/**
* Connect to db
* Must be called before other methods.
* @param string $dbhost The database host.
* @param string $dbuser The database username.
* @param string $dbpass The database username's password.
@ -527,7 +532,7 @@ class mysqli_native_moodle_database extends moodle_database {
* @param array $dboptions driver specific options
* @return bool success
*/
public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
public function raw_connect(string $dbhost, string $dbuser, string $dbpass, string $dbname, $prefix, array $dboptions=null): bool {
$driverstatus = $this->driver_installed();
if ($driverstatus !== true) {
@ -556,10 +561,19 @@ class mysqli_native_moodle_database extends moodle_database {
if ($dbhost and !empty($this->dboptions['dbpersist'])) {
$dbhost = "p:$dbhost";
}
$this->mysqli = @new mysqli($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket);
$this->mysqli = mysqli_init();
if (!empty($this->dboptions['connecttimeout'])) {
$this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->dboptions['connecttimeout']);
}
if ($this->mysqli->connect_errno !== 0) {
$dberr = $this->mysqli->connect_error;
$conn = null;
try {
$conn = $this->mysqli->real_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbsocket);
} catch (\Exception $e) {
$dberr = "$e";
}
if (!$conn) {
$dberr = $dberr ?: $this->mysqli->connect_error;
$this->mysqli = null;
throw new dml_connection_exception($dberr);
}
@ -568,16 +582,14 @@ class mysqli_native_moodle_database extends moodle_database {
$this->query_log_prevent();
if (isset($dboptions['dbcollation'])) {
$collationinfo = explode('_', $dboptions['dbcollation']);
$this->dboptions['dbcollation'] = $dboptions['dbcollation'];
$collation = $this->dboptions['dbcollation'] = $dboptions['dbcollation'];
} else {
$collationinfo = explode('_', $this->get_dbcollation());
$collation = $this->detect_collation();
}
$collationinfo = explode('_', $collation);
$charset = reset($collationinfo);
$this->query_start("--set_charset()", null, SQL_QUERY_AUX);
$this->mysqli->set_charset($charset);
$this->query_end(true);
// If available, enforce strict mode for the session. That guaranties
// standard behaviour under some situations, avoiding some MySQL nasty
@ -587,9 +599,7 @@ class mysqli_native_moodle_database extends moodle_database {
$si = $this->get_server_info();
if (version_compare($si['version'], '5.0.2', '>=')) {
$sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES'";
$this->query_start($sql, null, SQL_QUERY_AUX);
$result = $this->mysqli->query($sql);
$this->query_end($result);
}
// We can enable logging now.
@ -614,6 +624,40 @@ class mysqli_native_moodle_database extends moodle_database {
}
}
/**
* Gets db handle currently used with queries
* @return resource
*/
protected function get_db_handle() {
return $this->mysqli;
}
/**
* Sets db handle to be used with subsequent queries
* @param resource $dbh
* @return void
*/
protected function set_db_handle($dbh): void {
$this->mysqli = $dbh;
}
/**
* Check if The query qualifies for readonly connection execution
* Logging queries are exempt, those are write operations that circumvent
* standard query_start/query_end paths.
* @param int $type type of query
* @param string $sql
* @return bool
*/
protected function can_use_readonly(int $type, string $sql): bool {
// ... *_LOCK queries always go to master.
if (preg_match('/\b(GET|RELEASE)_LOCK/i', $sql)) {
return false;
}
return $this->read_slave_can_use_readonly($type, $sql);
}
/**
* Returns database server info array
* @return array Array containing 'description' and 'version' info

View file

@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/moodle_database.php');
require_once(__DIR__.'/moodle_read_slave_trait.php');
require_once(__DIR__.'/pgsql_native_moodle_recordset.php');
require_once(__DIR__.'/pgsql_native_moodle_temptables.php');
@ -36,6 +37,14 @@ require_once(__DIR__.'/pgsql_native_moodle_temptables.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class pgsql_native_moodle_database extends moodle_database {
use moodle_read_slave_trait {
select_db_handle as read_slave_select_db_handle;
can_use_readonly as read_slave_can_use_readonly;
query_start as read_slave_query_start;
}
/** @var array $dbhcursor keep track of open cursors */
private $dbhcursor = [];
/** @var resource $pgsql database resource */
protected $pgsql = null;
@ -110,7 +119,6 @@ class pgsql_native_moodle_database extends moodle_database {
/**
* Connect to db
* Must be called before other methods.
* @param string $dbhost The database host.
* @param string $dbuser The database username.
* @param string $dbpass The database username's password.
@ -120,7 +128,7 @@ class pgsql_native_moodle_database extends moodle_database {
* @return bool true
* @throws dml_connection_exception if error
*/
public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, array $dboptions=null) {
public function raw_connect(string $dbhost, string $dbuser, string $dbpass, string $dbname, $prefix, array $dboptions=null): bool {
if ($prefix == '' and !$this->external) {
//Enforce prefixes for everybody but mysql
throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily());
@ -160,6 +168,10 @@ class pgsql_native_moodle_database extends moodle_database {
$connection = "host='$this->dbhost' $port user='$this->dbuser' password='$pass' dbname='$this->dbname'";
}
if (!empty($this->dboptions['connecttimeout'])) {
$connection .= " connect_timeout=".$this->dboptions['connecttimeout'];
}
if (empty($this->dboptions['dbhandlesoptions'])) {
// ALTER USER and ALTER DATABASE are overridden by these settings.
$options = array('--client_encoding=utf8', '--standard_conforming_strings=on');
@ -232,6 +244,64 @@ class pgsql_native_moodle_database extends moodle_database {
}
}
/**
* Gets db handle currently used with queries
* @return resource
*/
protected function get_db_handle() {
return $this->pgsql;
}
/**
* Sets db handle to be used with subsequent queries
* @param resource $dbh
* @return void
*/
protected function set_db_handle($dbh): void {
$this->pgsql = $dbh;
}
/**
* Select appropriate db handle - readwrite or readonly
* @param int $type type of query
* @param string $sql
* @return void
*/
protected function select_db_handle(int $type, string $sql): void {
$this->read_slave_select_db_handle($type, $sql);
if (preg_match('/^DECLARE (crs\w*) NO SCROLL CURSOR/', $sql, $match)) {
$cursor = $match[1];
$this->dbhcursor[$cursor] = $this->pgsql;
}
if (preg_match('/^(?:FETCH \d+ FROM|CLOSE) (crs\w*)\b/', $sql, $match)) {
$cursor = $match[1];
$this->pgsql = $this->dbhcursor[$cursor];
}
}
/**
* Check if The query qualifies for readonly connection execution
* Logging queries are exempt, those are write operations that circumvent
* standard query_start/query_end paths.
* @param int $type type of query
* @param string $sql
* @return bool
*/
protected function can_use_readonly(int $type, string $sql): bool {
// ... pg_*lock queries always go to master.
if (preg_match('/\bpg_\w*lock/', $sql)) {
return false;
}
// ... a nuisance - temptables use this.
if (preg_match('/\bpg_constraint/', $sql) && $this->temptables->get_temptables()) {
return false;
}
return $this->read_slave_can_use_readonly($type, $sql);
}
/**
* Called before each db query.
@ -242,8 +312,8 @@ class pgsql_native_moodle_database extends moodle_database {
* @return void
*/
protected function query_start($sql, array $params=null, $type, $extrainfo=null) {
parent::query_start($sql, $params, $type, $extrainfo);
// pgsql driver tents to send debug to output, we do not need that ;-)
$this->read_slave_query_start($sql, $params, $type, $extrainfo);
// pgsql driver tends to send debug to output, we do not need that.
$this->last_error_reporting = error_reporting(0);
}
@ -733,8 +803,6 @@ class pgsql_native_moodle_database extends moodle_database {
list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
$this->query_start($sql, $params, SQL_QUERY_SELECT);
// For any query that doesn't explicitly specify a limit, we must use cursors to stop it
// loading the entire thing (unless the config setting is turned off).
$usecursors = !$limitnum && ($this->get_fetch_buffer_size() > 0);
@ -747,12 +815,14 @@ class pgsql_native_moodle_database extends moodle_database {
// Do the query to a cursor.
$sql = 'DECLARE ' . $cursorname . ' NO SCROLL CURSOR WITH HOLD FOR ' . $sql;
$result = pg_query_params($this->pgsql, $sql, $params);
} else {
$result = pg_query_params($this->pgsql, $sql, $params);
$cursorname = '';
}
$this->query_start($sql, $params, SQL_QUERY_SELECT);
$result = pg_query_params($this->pgsql, $sql, $params);
$this->query_end($result);
if ($usecursors) {
pg_free_result($result);

View 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);
}
}

View 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);
}
}

View 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();
}
}

View file

@ -5140,6 +5140,16 @@ class core_dml_testcase extends database_driver_testcase {
if (!isset($cfg->dboptions)) {
$cfg->dboptions = array();
}
// If we have a readonly slave situation, we need to either observe
// the latency, or if the latency is not specified we need to take
// the slave out because the table may not have propagated yet.
if (isset($cfg->dboptions['readonly'])) {
if (isset($cfg->dboptions['readonly']['latency'])) {
usleep(intval(1000000 * $cfg->dboptions['readonly']['latency']));
} else {
unset($cfg->dboptions['readonly']);
}
}
$DB2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
$DB2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);

View 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;
}
}

View 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;
}
}

View 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;
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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() {
}
}

View 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 [];
}
}