MDL-71000 lib/dml: New query type SQL_QUERY_AUX_READONLY
For SQL_QUERY_AUX queries that can go to readonly, conditions permitting.
This commit is contained in:
@@ -52,6 +52,10 @@ define('SQL_QUERY_STRUCTURE', 4);
|
||||
/** SQL_QUERY_AUX - Auxiliary query done by driver, setting connection config, getting table info, etc. */
|
||||
define('SQL_QUERY_AUX', 5);
|
||||
|
||||
/** SQL_QUERY_AUX_READONLY - Auxiliary query that can be done using the readonly connection:
|
||||
* database parameters, table/index/column lists, if not within transaction/ddl. */
|
||||
define('SQL_QUERY_AUX_READONLY', 6);
|
||||
|
||||
/**
|
||||
* Abstract class representing moodle database interface.
|
||||
* @link http://docs.moodle.org/dev/DML_functions
|
||||
@@ -414,9 +418,11 @@ abstract class moodle_database {
|
||||
|
||||
/**
|
||||
* This should be called before each db query.
|
||||
*
|
||||
* @param string $sql The query string.
|
||||
* @param array $params An array of parameters.
|
||||
* @param int $type The type of query. ( SQL_QUERY_SELECT | SQL_QUERY_AUX | SQL_QUERY_INSERT | SQL_QUERY_UPDATE | SQL_QUERY_STRUCTURE )
|
||||
* @param int $type The type of query ( SQL_QUERY_SELECT | SQL_QUERY_AUX_READONLY | SQL_QUERY_AUX |
|
||||
* SQL_QUERY_INSERT | SQL_QUERY_UPDATE | SQL_QUERY_STRUCTURE ).
|
||||
* @param mixed $extrainfo This is here for any driver specific extra information.
|
||||
* @return void
|
||||
*/
|
||||
@@ -433,6 +439,7 @@ abstract class moodle_database {
|
||||
switch ($type) {
|
||||
case SQL_QUERY_SELECT:
|
||||
case SQL_QUERY_AUX:
|
||||
case SQL_QUERY_AUX_READONLY:
|
||||
$this->reads++;
|
||||
break;
|
||||
case SQL_QUERY_INSERT:
|
||||
@@ -483,6 +490,7 @@ abstract class moodle_database {
|
||||
switch ($type) {
|
||||
case SQL_QUERY_SELECT:
|
||||
case SQL_QUERY_AUX:
|
||||
case SQL_QUERY_AUX_READONLY:
|
||||
throw new dml_read_exception($error, $sql, $params);
|
||||
case SQL_QUERY_INSERT:
|
||||
case SQL_QUERY_UPDATE:
|
||||
|
||||
@@ -57,8 +57,9 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* in the $written array and microtime() the event. 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
|
||||
* are used for transaction start/end, locking etc. In that respect, query_start() and
|
||||
* query_end() *must not* be used during the connection phase.
|
||||
* - SQL_QUERY_AUX_READONLY queries will use the master write handle if in a transaction.
|
||||
* - 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
|
||||
@@ -91,6 +92,7 @@ trait moodle_read_slave_trait {
|
||||
private $wantreadslave = false;
|
||||
private $readsslave = 0;
|
||||
private $slavelatency = 1;
|
||||
private $structurechange = false;
|
||||
|
||||
private $written = []; // Track tables being written to.
|
||||
private $readexclude = []; // Tables to exclude from using dbhreadonly.
|
||||
@@ -325,6 +327,10 @@ trait moodle_read_slave_trait {
|
||||
|
||||
// Transactions are done as AUX, we cannot play with that.
|
||||
switch ($type) {
|
||||
case SQL_QUERY_AUX_READONLY:
|
||||
// SQL_QUERY_AUX_READONLY may read the structure data.
|
||||
// We don't have a way to reliably determine whether it is safe to go to readonly if the structure has changed.
|
||||
return !$this->structurechange;
|
||||
case SQL_QUERY_SELECT:
|
||||
if ($this->transactions) {
|
||||
return false;
|
||||
@@ -358,6 +364,7 @@ trait moodle_read_slave_trait {
|
||||
}
|
||||
return false;
|
||||
case SQL_QUERY_STRUCTURE:
|
||||
$this->structurechange = true;
|
||||
foreach ($this->table_names($sql) as $tablename) {
|
||||
if (!in_array($tablename, $this->readexclude)) {
|
||||
$this->readexclude[] = $tablename;
|
||||
|
||||
@@ -176,7 +176,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
$sql = "SELECT engine
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config'";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($rec = $result->fetch_assoc()) {
|
||||
@@ -194,7 +194,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
|
||||
// Get the default database engine.
|
||||
$sql = "SELECT @@default_storage_engine engine";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($rec = $result->fetch_assoc()) {
|
||||
@@ -205,7 +205,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
if ($engine === 'MyISAM') {
|
||||
// we really do not want MyISAM for Moodle, InnoDB or XtraDB is a reasonable defaults if supported
|
||||
$sql = "SHOW STORAGE ENGINES";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
$engines = array();
|
||||
@@ -349,7 +349,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
|
||||
$sql = "SHOW VARIABLES LIKE 'innodb_file_format'";
|
||||
}
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($rec = $result->fetch_assoc()) {
|
||||
@@ -541,8 +541,8 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
|
||||
$this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions);
|
||||
|
||||
// dbsocket is used ONLY if host is NULL or 'localhost',
|
||||
// you can not disable it because it is always tried if dbhost is 'localhost'
|
||||
// The dbsocket option is used ONLY if host is null or 'localhost'.
|
||||
// You can not disable it because it is always tried if dbhost is 'localhost'.
|
||||
if (!empty($this->dboptions['dbsocket'])
|
||||
and (strpos($this->dboptions['dbsocket'], '/') !== false or strpos($this->dboptions['dbsocket'], '\\') !== false)) {
|
||||
$dbsocket = $this->dboptions['dbsocket'];
|
||||
@@ -696,7 +696,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
$this->tables = array();
|
||||
$prefix = str_replace('_', '\\_', $this->prefix);
|
||||
$sql = "SHOW TABLES LIKE '$prefix%'";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, $usecache ? SQL_QUERY_AUX_READONLY : SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
$len = strlen($this->prefix);
|
||||
@@ -723,7 +723,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
$indexes = array();
|
||||
$fixedtable = $this->fix_table_name($table);
|
||||
$sql = "SHOW INDEXES FROM $fixedtable";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = $this->mysqli->query($sql);
|
||||
try {
|
||||
$this->query_end($result);
|
||||
@@ -760,7 +760,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
WHERE table_name = '" . $this->prefix.$table . "'
|
||||
AND table_schema = '" . $this->dbname . "'
|
||||
ORDER BY ordinal_position";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end(true); // Don't want to throw anything here ever. MDL-30147
|
||||
|
||||
@@ -783,7 +783,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
$result->close();
|
||||
$fixedtable = $this->fix_table_name($table);
|
||||
$sql = "SHOW COLUMNS FROM $fixedtable";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end(true);
|
||||
if ($result === false) {
|
||||
@@ -1065,7 +1065,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
$charset = reset($collationinfo);
|
||||
|
||||
$sql = "SHOW COLLATION WHERE Collation ='$collation' AND Charset = '$charset'";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
if ($result->fetch_assoc()) {
|
||||
@@ -1404,28 +1404,10 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert multiple records into database as fast as possible.
|
||||
*
|
||||
* Order of inserts is maintained, but the operation is not atomic,
|
||||
* use transactions if necessary.
|
||||
*
|
||||
* This method is intended for inserting of large number of small objects,
|
||||
* do not use for huge objects with text or binary fields.
|
||||
*
|
||||
* @since Moodle 2.7
|
||||
*
|
||||
* @param string $table The database table to be inserted into
|
||||
* @param array|Traversable $dataobjects list of objects to be inserted, must be compatible with foreach
|
||||
* @return void does not return new record ids
|
||||
*
|
||||
* @throws coding_exception if data objects have different structure
|
||||
* @throws dml_exception A DML specific exception is thrown for any errors.
|
||||
* Get chunk size for multiple records insert
|
||||
* @return int
|
||||
*/
|
||||
public function insert_records($table, $dataobjects) {
|
||||
if (!is_array($dataobjects) and !$dataobjects instanceof Traversable) {
|
||||
throw new coding_exception('insert_records() passed non-traversable object');
|
||||
}
|
||||
|
||||
private function insert_chunk_size(): int {
|
||||
// MySQL has a relatively small query length limit by default,
|
||||
// make sure 'max_allowed_packet' in my.cnf is high enough
|
||||
// if you change the following default...
|
||||
@@ -1456,7 +1438,33 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
}
|
||||
}
|
||||
}
|
||||
return $chunksize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert multiple records into database as fast as possible.
|
||||
*
|
||||
* Order of inserts is maintained, but the operation is not atomic,
|
||||
* use transactions if necessary.
|
||||
*
|
||||
* This method is intended for inserting of large number of small objects,
|
||||
* do not use for huge objects with text or binary fields.
|
||||
*
|
||||
* @since Moodle 2.7
|
||||
*
|
||||
* @param string $table The database table to be inserted into
|
||||
* @param array|Traversable $dataobjects list of objects to be inserted, must be compatible with foreach
|
||||
* @return void does not return new record ids
|
||||
*
|
||||
* @throws coding_exception if data objects have different structure
|
||||
* @throws dml_exception A DML specific exception is thrown for any errors.
|
||||
*/
|
||||
public function insert_records($table, $dataobjects) {
|
||||
if (!is_array($dataobjects) && !$dataobjects instanceof Traversable) {
|
||||
throw new coding_exception('insert_records() passed non-traversable object');
|
||||
}
|
||||
|
||||
$chunksize = $this->insert_chunk_size();
|
||||
$columns = $this->get_columns($table, true);
|
||||
$fields = null;
|
||||
$count = 0;
|
||||
@@ -2023,12 +2031,12 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
}
|
||||
|
||||
$sql = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
|
||||
$sql = "START TRANSACTION";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
}
|
||||
@@ -2044,7 +2052,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
}
|
||||
|
||||
$sql = "COMMIT";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
}
|
||||
@@ -2060,7 +2068,7 @@ class mysqli_native_moodle_database extends moodle_database {
|
||||
}
|
||||
|
||||
$sql = "ROLLBACK";
|
||||
$this->query_start($sql, NULL, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$result = $this->mysqli->query($sql);
|
||||
$this->query_end($result);
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
query_end as read_slave_query_end;
|
||||
}
|
||||
|
||||
/** @var array $serverinfo cache */
|
||||
private $serverinfo = [];
|
||||
|
||||
/** @var array $dbhcursor keep track of open cursors */
|
||||
private $dbhcursor = [];
|
||||
|
||||
@@ -336,7 +339,12 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
error_reporting($this->last_error_reporting);
|
||||
try {
|
||||
$this->read_slave_query_end($result);
|
||||
if ($this->savepointpresent and $this->last_type != SQL_QUERY_AUX and $this->last_type != SQL_QUERY_SELECT) {
|
||||
if ($this->savepointpresent &&
|
||||
!in_array(
|
||||
$this->last_type,
|
||||
[SQL_QUERY_AUX, SQL_QUERY_AUX_READONLY, SQL_QUERY_SELECT],
|
||||
true
|
||||
)) {
|
||||
$res = @pg_query($this->pgsql, "RELEASE SAVEPOINT moodle_pg_savepoint; SAVEPOINT moodle_pg_savepoint");
|
||||
if ($res) {
|
||||
pg_free_result($res);
|
||||
@@ -357,14 +365,16 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
* Returns database server info array
|
||||
* @return array Array containing 'description' and 'version' info
|
||||
*/
|
||||
public function get_server_info() {
|
||||
static $info;
|
||||
if (!$info) {
|
||||
$this->query_start("--pg_version()", null, SQL_QUERY_AUX);
|
||||
$info = pg_version($this->pgsql);
|
||||
public function get_server_info(): array {
|
||||
if (empty($this->serverinfo)) {
|
||||
$this->query_start('--pg_version()', null, SQL_QUERY_AUX);
|
||||
$this->serverinfo = pg_version($this->pgsql);
|
||||
$this->query_end(true);
|
||||
}
|
||||
return array('description'=>$info['server'], 'version'=>$info['server']);
|
||||
return [
|
||||
'description' => $this->serverinfo['server'],
|
||||
'version' => $this->serverinfo['server'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,7 +410,7 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
WHERE c.relname LIKE '$prefix%' ESCAPE '|'
|
||||
AND c.relkind = 'r'
|
||||
AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = pg_query($this->pgsql, $sql);
|
||||
$this->query_end($result);
|
||||
|
||||
@@ -492,7 +502,7 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
WHERE i.tablename = '$tablename'
|
||||
AND (i.schemaname = current_schema() OR ns.oid = pg_my_temp_schema())";
|
||||
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = pg_query($this->pgsql, $sql);
|
||||
$this->query_end($result);
|
||||
|
||||
@@ -545,7 +555,7 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())
|
||||
ORDER BY a.attnum";
|
||||
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = pg_query($this->pgsql, $sql);
|
||||
$this->query_end($result);
|
||||
|
||||
@@ -766,8 +776,8 @@ class pgsql_native_moodle_database extends moodle_database {
|
||||
*/
|
||||
public function setup_is_unicodedb() {
|
||||
// Get PostgreSQL server_encoding value
|
||||
$sql = "SHOW server_encoding";
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$sql = 'SHOW server_encoding';
|
||||
$this->query_start($sql, null, SQL_QUERY_AUX_READONLY);
|
||||
$result = pg_query($this->pgsql, $sql);
|
||||
$this->query_end($result);
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace core;
|
||||
|
||||
use moodle_database;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/fixtures/read_slave_moodle_database_mock_mysqli.php');
|
||||
@@ -45,7 +47,7 @@ class dml_mysqli_read_slave_test extends \base_testcase {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_lock() : void {
|
||||
public function test_lock(): void {
|
||||
$DB = new read_slave_moodle_database_mock_mysqli();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
@@ -62,11 +64,11 @@ class dml_mysqli_read_slave_test extends \base_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test readonly connection failure with real mysqli connection
|
||||
* Test readonly handle is used for SQL_QUERY_AUX_READONLY queries.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_real_readslave_connect_fail() : void {
|
||||
public function test_aux_readonly(): void {
|
||||
global $DB;
|
||||
|
||||
if ($DB->get_dbfamily() != 'mysql') {
|
||||
@@ -76,14 +78,87 @@ class dml_mysqli_read_slave_test extends \base_testcase {
|
||||
// Open second connection.
|
||||
$cfg = $DB->export_dbconfig();
|
||||
if (!isset($cfg->dboptions)) {
|
||||
$cfg->dboptions = array();
|
||||
$cfg->dboptions = [];
|
||||
}
|
||||
$cfg->dboptions['readonly'] = [
|
||||
'instance' => [$cfg->dbhost]
|
||||
];
|
||||
$cfg->dboptions['dbengine'] = null;
|
||||
$cfg->dboptions['bulkinsertsize'] = null;
|
||||
|
||||
// Get a separate disposable db connection handle with guaranteed 'readonly' config.
|
||||
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
|
||||
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
|
||||
|
||||
$reads = $db2->perf_get_reads();
|
||||
$readsprimary = $reads - $db2->perf_get_reads_slave();
|
||||
|
||||
// Readonly handle queries.
|
||||
|
||||
$db2->setup_is_unicodedb();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_tables();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_indexes('course');
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_columns('course');
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
// Readwrite handle queries.
|
||||
|
||||
if (PHP_INT_SIZE !== 4) {
|
||||
$rc = new \ReflectionClass(\mysqli_native_moodle_database::class);
|
||||
$rcm = $rc->getMethod('insert_chunk_size');
|
||||
|
||||
$rcm->setAccessible(true);
|
||||
$rcm->invoke($db2);
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertGreaterThan($readsprimary, $readsprimary = $reads - $db2->perf_get_reads_slave());
|
||||
}
|
||||
|
||||
$db2->get_dbengine();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertGreaterThan($readsprimary, $readsprimary = $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->diagnose();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertGreaterThan($readsprimary, $readsprimary = $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_row_format('course');
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertGreaterThan($readsprimary, $readsprimary = $reads - $db2->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 = [];
|
||||
}
|
||||
$cfg->dboptions['readonly'] = [
|
||||
'instance' => ['host.that.is.not'],
|
||||
'connecttimeout' => 1
|
||||
];
|
||||
|
||||
$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);
|
||||
$this->assertTrue(count($db2->get_records('user')) > 0);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
|
||||
namespace core;
|
||||
|
||||
use moodle_database;
|
||||
use xmldb_table;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/fixtures/read_slave_moodle_database_mock_pgsql.php');
|
||||
@@ -38,13 +41,13 @@ require_once(__DIR__.'/fixtures/read_slave_moodle_database_mock_pgsql.php');
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \pgsql_native_moodle_database
|
||||
*/
|
||||
class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
class dml_pgsql_read_slave_test extends \advanced_testcase {
|
||||
/**
|
||||
* Test correct database handles are used for cursors
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_cursors() : 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.
|
||||
@@ -73,6 +76,18 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
$DB->query_start($sql, null, SQL_QUERY_AUX);
|
||||
$this->assertTrue($DB->db_handle_is_rw());
|
||||
$DB->query_end(null);
|
||||
|
||||
// Close the non-written to table cursor.
|
||||
$sql = 'CLOSE crs1';
|
||||
$DB->query_start($sql, [], SQL_QUERY_AUX);
|
||||
$this->assertTrue($DB->db_handle_is_ro());
|
||||
$DB->query_end(null);
|
||||
|
||||
// Close the written to table cursor.
|
||||
$sql = 'CLOSE crs2';
|
||||
$DB->query_start($sql, [], SQL_QUERY_AUX);
|
||||
$this->assertTrue($DB->db_handle_is_rw());
|
||||
$DB->query_end(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +95,7 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_pg_table() : void {
|
||||
public function test_read_pg_table(): void {
|
||||
$DB = new read_slave_moodle_database_mock_pgsql();
|
||||
|
||||
$this->assertEquals(0, $DB->perf_get_reads_slave());
|
||||
@@ -97,7 +112,7 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_pg_lock_table() : 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());
|
||||
@@ -111,19 +126,16 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Test readonly handle is used for SQL_QUERY_AUX_READONLY queries.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_temp_table() : void {
|
||||
public function test_aux_readonly(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
if ($DB->get_dbfamily() != 'postgres') {
|
||||
$this->markTestSkipped("Not postgres");
|
||||
$this->markTestSkipped('Not postgres');
|
||||
}
|
||||
|
||||
// Open second connection.
|
||||
@@ -138,12 +150,81 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
}
|
||||
|
||||
// Get a separate disposable db connection handle with guaranteed 'readonly' config.
|
||||
$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);
|
||||
|
||||
$reads = $db2->perf_get_reads();
|
||||
$readsprimary = $reads - $db2->perf_get_reads_slave();
|
||||
|
||||
// Readonly handle queries.
|
||||
|
||||
$db2->get_server_info();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertGreaterThan($readsprimary, $readsprimary = $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->setup_is_unicodedb();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_tables();
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_indexes('course');
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
$db2->get_columns('course');
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertEquals($readsprimary, $reads - $db2->perf_get_reads_slave());
|
||||
|
||||
// Readwrite handle queries.
|
||||
|
||||
$tablename = 'test_table';
|
||||
$table = new xmldb_table($tablename);
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$dbman = $db2->get_manager();
|
||||
$dbman->create_table($table);
|
||||
$db2->get_columns($tablename);
|
||||
$this->assertGreaterThan($reads, $reads = $db2->perf_get_reads());
|
||||
$this->assertGreaterThan($readsprimary, $reads - $db2->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 {
|
||||
global $DB;
|
||||
|
||||
if ($DB->get_dbfamily() != 'postgres') {
|
||||
$this->markTestSkipped('Not postgres');
|
||||
}
|
||||
|
||||
// Open second connection.
|
||||
$cfg = $DB->export_dbconfig();
|
||||
if (!isset($cfg->dboptions)) {
|
||||
$cfg->dboptions = [];
|
||||
}
|
||||
if (!isset($cfg->dboptions['readonly'])) {
|
||||
$cfg->dboptions['readonly'] = [
|
||||
'instance' => [$cfg->dbhost]
|
||||
];
|
||||
}
|
||||
|
||||
// Get a separate disposable db connection handle with guaranteed 'readonly' config.
|
||||
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
|
||||
$db2->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
|
||||
|
||||
$dbman = $db2->get_manager();
|
||||
|
||||
$table = new \xmldb_table('silly_test_table');
|
||||
$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']);
|
||||
@@ -158,7 +239,7 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
$db2->get_records('silly_test_table');
|
||||
$this->assertEquals($reads, $db2->perf_get_reads_slave());
|
||||
|
||||
$table2 = new \xmldb_table('silly_test_table2');
|
||||
$table2 = new xmldb_table('silly_test_table2');
|
||||
$table2->add_field('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
|
||||
$table2->add_field('msg', XMLDB_TYPE_CHAR, 255);
|
||||
$table2->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
|
||||
@@ -179,11 +260,11 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_real_readslave_connect_fail() : void {
|
||||
public function test_real_readslave_connect_fail(): void {
|
||||
global $DB;
|
||||
|
||||
if ($DB->get_dbfamily() != 'postgres') {
|
||||
$this->markTestSkipped("Not postgres");
|
||||
$this->markTestSkipped('Not postgres');
|
||||
}
|
||||
|
||||
// Open second connection.
|
||||
@@ -196,7 +277,7 @@ class dml_pgsql_read_slave_test extends \base_testcase {
|
||||
'connecttimeout' => 1
|
||||
];
|
||||
|
||||
$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);
|
||||
$this->assertTrue(count($db2->get_records('user')) > 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user