first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,164 @@
<?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
*/
namespace core;
use moodle_database;
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
* @covers \mysqli_native_moodle_database
*/
class dml_mysqli_read_slave_test 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->assertTrue($DB->db_handle_is_rw());
$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->assertTrue($DB->db_handle_is_rw());
$DB->query_end(null);
$this->assertEquals(0, $DB->perf_get_reads_slave());
}
/**
* Test readonly handle is used for SQL_QUERY_AUX_READONLY queries.
*
* @return void
*/
public function test_aux_readonly(): 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' => [$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->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->connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
$this->assertTrue(count($db2->get_records('user')) > 0);
}
}
+284
View File
@@ -0,0 +1,284 @@
<?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
*/
namespace core;
use moodle_database;
use xmldb_table;
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
* @covers \pgsql_native_moodle_database
*/
class dml_pgsql_read_slave_test extends \advanced_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->assertTrue($DB->db_handle_is_ro());
$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->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);
}
/**
* 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->assertTrue($DB->db_handle_is_ro());
$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->assertTrue($DB->db_handle_is_rw());
$DB->query_end(null);
$this->assertEquals(0, $DB->perf_get_reads_slave());
}
}
/**
* Test readonly handle is used for SQL_QUERY_AUX_READONLY queries.
*
* @return void
*/
public function test_aux_readonly(): void {
global $DB;
$this->resetAfterTest();
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);
$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->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);
// We need to go through the creation proces twice.
// create_temp_table() performs some reads before the temp table is created.
// First time around those reads should go to ro ...
$reads = $db2->perf_get_reads_slave();
$db2->get_columns('silly_test_table');
$db2->get_records('silly_test_table');
$this->assertEquals($reads, $db2->perf_get_reads_slave());
$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']);
$dbman->create_temp_table($table2);
// ... but once the first temp table is created no more ro reads should occur.
$db2->get_columns('silly_test_table2');
$db2->get_records('silly_test_table2');
$this->assertEquals($reads, $db2->perf_get_reads_slave());
// Make database driver happy.
$dbman->drop_table($table2);
$dbman->drop_table($table);
}
/**
* 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);
}
}
+573
View File
@@ -0,0 +1,573 @@
<?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
*/
namespace core;
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');
require_once(__DIR__.'/../../tests/fixtures/event_fixtures.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
* @covers \moodle_read_slave_trait
*/
class dml_read_slave_test 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->assertMatchesRegularExpression('/^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());
$handle = $DB->get_records('table');
$this->assertEquals('test_rw::test:test', $handle);
$this->assertEquals(0, $DB->perf_get_reads_slave());
$handle = $DB->get_records_sql("SELECT * FROM {table2} JOIN {table}");
$this->assertEquals('test_rw::test:test', $handle);
$this->assertEquals(0, $DB->perf_get_reads_slave());
sleep(1);
$handle = $DB->get_records('table');
$this->assert_readonly_handle($handle);
$this->assertEquals(1, $DB->perf_get_reads_slave());
$handle = $DB->get_records('table2');
$this->assert_readonly_handle($handle);
$this->assertEquals(2, $DB->perf_get_reads_slave());
$handle = $DB->get_records_sql("SELECT * FROM {table2} JOIN {table}");
$this->assert_readonly_handle($handle);
$this->assertEquals(3, $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
* @covers ::can_use_readonly
* @covers ::commit_delegated_transaction
*/
public function test_transaction(): void {
$DB = $this->new_db(true);
$this->assertNull($DB->get_dbhwrite());
$skip = false;
$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);
} else {
$skip = true;
}
// Exceeded latency time, use ro handle.
$handle = $DB->get_records_sql("SELECT * FROM {table}");
$this->assert_readonly_handle($handle);
if ($skip) {
$this->markTestSkipped("Delay too long to test write handle immediately after transaction");
}
}
/**
* Test readonly handle is not used immediately after update
* Test last written time is adjusted post-write,
* so the latency parameter is applied properly.
*
* @return void
* @covers ::can_use_readonly
* @covers ::query_end
*/
public function test_long_update(): void {
$DB = $this->new_db(true);
$this->assertNull($DB->get_dbhwrite());
$skip = false;
list($sql, $params, $ptype) = $DB->fix_sql_params("UPDATE {table} SET a = 1 WHERE id = 1");
$DB->with_query_start_end($sql, $params, SQL_QUERY_UPDATE, function ($dbh) use (&$now) {
sleep(1);
$now = microtime(true);
});
// This condition should always evaluate true, however we need to
// safeguard from an unaccounted delay that can break this test.
if (microtime(true) - $now < 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);
} else {
$skip = true;
}
// Exceeded latency time, use ro handle.
$handle = $DB->get_records_sql("SELECT * FROM {table}");
$this->assert_readonly_handle($handle);
if ($skip) {
$this->markTestSkipped("Delay too long to test write handle immediately after transaction");
}
}
/**
* Test readonly handle is not used with events
* when the latency parameter is applied properly.
*
* @return void
* @covers ::can_use_readonly
* @covers ::commit_delegated_transaction
*/
public function test_transaction_with_events(): void {
$this->with_global_db(function () {
global $DB;
$DB = $this->new_db(true, ['test_ro'], read_slave_moodle_database_special::class);
$DB->set_tables([
'config_plugins' => [
'columns' => [
'plugin' => (object)['meta_type' => ''],
]
]
]);
$this->assertNull($DB->get_dbhwrite());
$called = false;
$transaction = $DB->start_delegated_transaction();
$now = microtime(true);
$observers = [
[
'eventname' => '\core_tests\event\unittest_executed',
'callback' => function (\core_tests\event\unittest_executed $event) use ($DB, $now, &$called) {
$called = true;
$this->assertFalse($DB->is_transaction_started());
// 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_p("SELECT * FROM {table}");
$this->assertEquals('test_rw::test:test', $handle);
// Make sure enough time passes.
sleep(1);
} else {
$this->markTestSkipped("Delay too long to test write handle immediately after transaction");
}
// Exceeded latency time, use ro handle.
$handle = $DB->get_records_sql_p("SELECT * FROM {table}");
$this->assertEquals('test_ro::test:test', $handle);
},
'internal' => 0,
],
];
\core\event\manager::phpunit_replace_observers($observers);
$handle = $DB->get_records_sql_p("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);
$event = \core_tests\event\unittest_executed::create([
'context' => \context_system::instance(),
'other' => ['sample' => 1]
]);
$event->trigger();
$transaction->allow_commit();
$this->assertTrue($called);
});
}
/**
* 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->read('dummy');
$this->assertEquals(0, $DB->perf_get_reads_slave());
$this->assertTrue($DB->perf_get_reads() > 0);
});
\core\session\manager::restart_with_write_lock(false);
}
}
+221
View File
@@ -0,0 +1,221 @@
<?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/>.
namespace core;
use core\dml\table;
use xmldb_table;
/**
* DML Table tests.
*
* @package core
* @category test
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \core\dml\table
*/
class dml_table_test extends \database_driver_testcase {
/**
* Data provider for various \core\dml\table method tests.
*
* @return array
*/
public function get_field_select_provider(): array {
return [
'single field' => [
'tablename' => 'test_table_single',
'fieldlist' => [
'id' => ['id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null],
],
'primarykey' => 'id',
'fieldprefix' => 'ban',
'tablealias' => 'banana',
'banana.id AS banid',
],
'multiple fields' => [
'tablename' => 'test_table_multiple',
'fieldlist' => [
'id' => ['id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null],
'course' => ['course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'],
'name' => ['name', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala'],
],
'primarykey' => 'id',
'fieldprefix' => 'ban',
'tablealias' => 'banana',
'banana.id AS banid, banana.course AS bancourse, banana.name AS banname',
],
];
}
/**
* Ensure that \core\dml\table::get_field_select() works as expected.
*
* @dataProvider get_field_select_provider
* @covers ::get_field_select
* @param string $tablename The name of the table
* @param array $fieldlist The list of fields
* @param string $primarykey The name of the primary key
* @param string $fieldprefix The prefix to use for each field
* @param string $tablealias The table AS alias name
* @param string $expected The expected SQL
*/
public function test_get_field_select(
string $tablename,
array $fieldlist,
string $primarykey,
string $fieldprefix,
string $tablealias,
string $expected
): void {
$dbman = $this->tdb->get_manager();
$xmldbtable = new xmldb_table($tablename);
$xmldbtable->setComment("This is a test'n drop table. You can drop it safely");
foreach ($fieldlist as $args) {
call_user_func_array([$xmldbtable, 'add_field'], $args);
}
$xmldbtable->add_key('primary', XMLDB_KEY_PRIMARY, [$primarykey]);
$dbman->create_table($xmldbtable);
$table = new table($tablename, $tablealias, $fieldprefix);
$this->assertEquals($expected, $table->get_field_select());
}
/**
* Data provider for \core\dml\table::extract_from_result() tests.
*
* @return array
*/
public function extract_from_result_provider(): array {
return [
'single table' => [
'fieldlist' => [
'id' => ['id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null],
'course' => ['course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'],
'flag' => ['flag', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala'],
],
'primarykey' => 'id',
'prefix' => 's',
'result' => (object) [
'sid' => 1,
'scourse' => 42,
'sflag' => 'foo',
],
'expectedrecord' => (object) [
'id' => 1,
'course' => 42,
'flag' => 'foo',
],
],
'single table amongst others' => [
'fieldlist' => [
'id' => ['id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null],
'course' => ['course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'],
'flag' => ['flag', XMLDB_TYPE_CHAR, '255', null, null, null, 'lala'],
],
'primarykey' => 'id',
'prefix' => 's',
'result' => (object) [
'sid' => 1,
'scourse' => 42,
'sflag' => 'foo',
'oid' => 'id',
'ocourse' => 'course',
'oflag' => 'flag',
],
'expectedrecord' => (object) [
'id' => 1,
'course' => 42,
'flag' => 'foo',
],
],
];
}
/**
* Ensure that \core\dml\table::extract_from_result() works as expected.
*
* @dataProvider extract_from_result_provider
* @covers ::extract_from_result
* @param array $fieldlist The list of fields
* @param string $primarykey The name of the primary key
* @param string $fieldprefix The prefix to use for each field
* @param \stdClass $result The result of the get_records_sql
* @param \stdClass $expected The expected output
*/
public function test_extract_fields_from_result(
array $fieldlist,
string $primarykey,
string $fieldprefix,
\stdClass $result,
\stdClass $expected
): void {
$dbman = $this->tdb->get_manager();
$tablename = 'test_table_extraction';
$xmldbtable = new xmldb_table($tablename);
$xmldbtable->setComment("This is a test'n drop table. You can drop it safely");
foreach ($fieldlist as $args) {
call_user_func_array([$xmldbtable, 'add_field'], $args);
}
$xmldbtable->add_key('primary', XMLDB_KEY_PRIMARY, [$primarykey]);
$dbman->create_table($xmldbtable);
$table = new table($tablename, 'footable', $fieldprefix);
$this->assertEquals($expected, $table->extract_from_result($result));
}
/**
* Ensure that \core\dml\table::get_from_sql() works as expected.
*
* @dataProvider get_field_select_provider
* @covers ::get_from_sql
* @param string $tablename The name of the table
* @param array $fieldlist The list of fields
* @param string $primarykey The name of the primary key
* @param string $fieldprefix The prefix to use for each field
* @param string $tablealias The table AS alias name
* @param string $expected The expected SQL
*/
public function test_get_from_sql(
string $tablename,
array $fieldlist,
string $primarykey,
string $fieldprefix,
string $tablealias,
string $expected
): void {
$dbman = $this->tdb->get_manager();
$tablename = 'test_table_extraction';
$xmldbtable = new xmldb_table($tablename);
$xmldbtable->setComment("This is a test'n drop table. You can drop it safely");
foreach ($fieldlist as $args) {
call_user_func_array([$xmldbtable, 'add_field'], $args);
}
$xmldbtable->add_key('primary', XMLDB_KEY_PRIMARY, [$primarykey]);
$dbman->create_table($xmldbtable);
$table = new table($tablename, $tablealias, $fieldprefix);
$this->assertEquals("{{$tablename}} {$tablealias}", $table->get_from_sql());
}
}
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
제 1장 총 칙
제 1조 (개요)
이 약관은 전기통신사업법 및 동법 시행령에 의거 ㈜네오플(이하 "회사")이 제공하는 서비스 (이하 "서비스")의 이용조건, 절차, 의무, 책임사항 및 이용에 필요한 기타사항을 규정합니다.
제 2조 (목적)
이 약관은 다음과 같은 내용을 목적으로 합니다.
1. 안정적인 서비스 제공 및 서비스, 시스템의 보호를 목적으로 한다.
2. 이용자의 안정적인 서비스 이용 및 이용자의 데이타 보호를 목적으로 한다.
3. 네트워크 및 서비스 이용자 네트워크의 안정성, 프라이버시, 보안성 유지를 목적으로 한다.
4. 네트워크 및 서비스의 사용 원칙 가이드라인 제시를 목적으로 한다.
제 3조 (약관의 효력과 변경)
1. 회사는 귀하가 본 약관 내용에 동의하는 것을 조건으로 서비스를 제공 합니다.
2. 이 약관은 서비스 내에 게시하여 공시함으로써 효력을 발생합니다.
3. 당사의 서비스 제공 행위 및 귀하의 서비스 사용 행위에는 본 약관이 우선적으로 적용될 것입니다.
4. 회사는 이 약관을 개정할 경우에는 적용일자 및 개정사유를 명시하여 현행약관과 함께 초기화면에 그 적용일자 7일이전부터 적용일자 전일까지 공지합니다.
5. 이용자는 회원등록을 취소(회원탈퇴)할 수 있으며, 계속 사용의 경우는 약관 변경에 대한 동의로 간주됩니다. 변경된 약관은 공지와 동시에 그 효력이 발생됩니다.
제 4조 (약관외 준칙)
1. 서비스 이용에 관하여는 이 약관을 적용하며, 이 약관에 명시되지 아니 한 사항에 대하여는 전기통신 기본법, 전기통신사업법, 정보통신망 이용 촉진 및 정보보호 등에 관한 법률(이하통신망법), 기타 관련법령 및 회사의 공지,이용안내를 적용 합니다.
2. 이 약관의 공지 및 변경사항은 회사의 지정된 홈페이지(http://www.candybar.co.kr)에 게시하는 방법으로 공지합니다.
제 5조 (용어의 정의)
이 약관에서 사용하는 용어의 정의는 다음과 같습니다.
1. 이용자 : 서비스 이용을 신청하고 회사가 이를 승낙하여 회원ID를 발급 받은 자를 말합니다.
2. 가 입 : 회사가 제공하는 양식에 해당 정보를 기입하고, 이 약관에 동의하여 서비스 이용계약을 완료시키는 행위를 말합니다.
3. 회 원 : 회사에 개인 정보를 제공하여 회원 등록을 한 자로서, 회사가 제공하는 서비스를 이용할 수 있는 자를 말합니다.
4. 캔디바 ID : 회원의 서비스 이용을 위하여 회원이 신청하고 회사가 승인하는 문자 및 숫자의 조합을 말합니다.
5. 비밀번호 : 회원ID가 일치하는지를 확인하고 자신의 비밀보호를 위하여 이용자 자신이 선정한 문자와 숫자의 조합을 말합니다.
6. 탈퇴 및 해지 : 회원이 이용계약을 종료 시키는 행위를 말합니다.
제 2장 서비스 이용 계약
제 6조 (이용 계약의 성립)
1. 이용계약은 당사 소정의 가입신청 양식에서 요구하는 사항을 기록하여 가입을 완료 하는것으로 성립함을 원칙으로 합니다.
2. 이용신청은 이용신청자 자신의 실명으로 하여야 합니다. 실명이 아닌경우는 여러가지 불이익을 받을수 도 있습니다.
3. 이용계약은 서비스 이용희망자의 이용약관 동의 후 이용 신청에 대하여 회사가 승낙함으로써 성립합니다.
4. 서비스 이용 희망자가 14세 미만의 미성년자 또는 한정치산자인 경우 부모등 법정대리인의 동의를 받아 이용신청하며 납입책임자는 법정대리인으로 합니다. 또한 금치산자인 경우에는 법정대리인 을 이용자 및 납입책임자로 하여 신청합니다.
제 7조 (이용신청의 승낙)
1. 회사는 제 6조의 규정에 의한 서비스 이용희망자에 대하여 업무수행상 또는 기술상 지장이 없는 경우에는 원칙적으로 접수순서에 따라 이용신청을 승낙합니다.
2. 이용자의 이용신청에 대하여 회사가 이를 승낙한 경우, 회사는 회원 ID와 기타 회사가 필요하다고 인정하는 내용을 이용자에게 통지합니다.
제 8조 (이용신청에 대한 불승낙과 승낙의 보류)
1. 이용자가 신청한 이용 아이디(ID)가 이미 다른 이용자에 의해 쓰여지고 있는 경우 승낙을 하지 아니할 수 있습니다.
2. 회사는 다음 각호에 해당하는 이용신청에 대하여는 승낙을 하지 아니할 수 있습니다.
1) 타인 명의의 신청
2) 허위의 신청이거나 허위사실을 기재한 경우 예) 주민등록 생성기를 이용한 비실명 가입
3) 기타 이용신청고객의 귀책사유로 이용승낙이 곤란한 경우
3. 회사는 전항의 경우에는 이를 이용신청고객에게 가능한 빠른 시일 내에 통지하여야 합니다.
4. 회사는 이용신청고객이 미성년자, 한정치산자인 경우에 법정대리인(부모 등)의 동의없는 이용신청에 대하여 승낙을 보류할 수 있습니다.
5. 회사는 이용신청이 불승낙되거나 승낙을 제한하는 경우에는 이를 이용신청자에게 즉시 통보합니다.
제 9조 (이용 아이디 관리 및 변경)
1. 이용 아이디(ID) 및 비밀번호에 대한 모든 관리책임은 이용자에게 있습니다.
2. 이용 아이디(ID)는 다음에 해당하는 경우에는 이용자와 합의하여 변경할 수 있습니다.
(단, 이용 아이디(ID)를 변경할 경우 기존 이용ID는 소멸됩니다.)
1) 이용 아이디(ID)가 이용자의 전화번호 또는 주민등록번호 등으로 등록되어 사생활 침해가 우려 되는 경우
2) 타인에게 혐오감을 주거나 미풍양속에 어긋나는 경우
3) 기타 회사가 인정하는 합리적인 사유가 있는 경우
3. 회사는 이용 아이디(ID)에 의하여 서비스 이용요금 청구 및 게시판 관리 등 제반 이용자 관리 업무를 수행하며 이용자는 이용 아이디(ID)를 공유, 양도 또는 변경할 수 없습니다. 단, 그 사유가 명백하고 회사가 인정하는 경우에는 그러하지 아니 합니다.
4. 이용자가 신청 또는 변경하여 사용하는 이용 아이디(ID) 및 비밀번호에 의하여 발생하는 서비스 이용상의 과실 또는 제3자에 의한 부정사용등에 대한 모든 책임은 이용자에게 있습니다. 단, 회사의 고의 또는 중대한 과실이 있는 경우에는 그러하지 아니합니다.
5. 기타 아이디의 관리 및 변경 등에 관한 사항은 이 약관과 회사의 공지, 이용안내 에서 정하는 바에 의합니다.
제 3장 계약당사자의 의무 및 책임
제 10조 (회사의 의무)
1. 회사는 이용자로부터 제기되는 의견이나 불만이 정당하다고 인정할 경우에는 즉시 처리하여야 합니다. 즉시 처리가 곤란한 경우에는 그 사유와 처리일정을 서면 또는 전화 등으로 통보합니다.
2. 회사는 서비스 제공과 관련하여 취득한 이용자의 정보를 본인의 동의없이 타인에게 누설 배포할 수 없으며 서비스 관련 업무 이외의 목적으로도 사용할 수 없습니다.
3. 제2항의 경우 관계법령에 의한 규정에 의하여 수사상의 목적으로 관계기관으로부터 요구받은 경우나 정보통신윤리위원회의 요청이 있는 경우 또는 통신망법 24조 1항 '정보통신서비스의 제공에 따른 요금정산을 위하여 필요한 경우'에는 개인정보의 이용 혹은 제 3자에게 제공될수 있으며 그 범위는 회사의 개인정보보호정책을 따릅니다.
4. 회사는 계속적이고 안정적인 서비스 제공을 위하여 설비에 장애가 생기거나 멸실된 경우에는 지체없이 이를 수리 또는 복구하며, 서비스를 다시 이용할 수 있게 된 경우 이 사실을 이용자에게 통지하여야 합니다. 단 천재지변, 비상사태 또는 그밖의 부득이한 경우에는 서비스를 일시 중단하거나 중지할 수 있습니다.
5. 회사는 이용계약의 체결, 계약사항의 변경 및 해지 등 이용자와의 계약에 관련된 절차 및 내용 등에 있어서 이용자에게 편의를 제공하도록 노력합니다.
6. 회사는 이용자가 안전하게 당사서비스를 이용할 수 있도록 이용자의 개인정보(신용정보 포함)보호를 위한 보안시스템을 갖추어야 합니다.
7. 회사는 이용자가 제11조의 이용자의 의무를 위반한 경우 및 고의 또는 중대한 과실로 회사에 손해를 입힌 경우에는 사전 통보 없이 이용계약을 해지하거나 또는 기간을 정하여 서비스의 이용을 중지할 수 있습니다.
제 11 조 (이용자의 의무)
1. 이용자는 이 약관에서 규정하는 사항과 서비스 이용안내 또는 주의사항을 준수하여야 하며,기타 회사의 업무수행에 현저한 지장을 초래하는 행위를 하여서는 아니 됩니다.
2. 이용자는 서비스를 이용함에 있어 문제 발생소지 정보의 책임은 이용자에게 있습니다.
3. 이용자는 서비스를 이용하여 얻은 정보를 가공, 판매하는 행위 등 게재 된 자료를 상업적으로 이용할 수 없으며 이를 위반하여 발생하는 제반 문제에 대한 책임은 이용자에게 있습니다.
4. 이용자는 이용계약에 따라 요금을 지불하여야 하며, 서비스 이용요금의 미납으로 인하여 발생되는 모든 문제에 대한 책임은 이용자에게 있습니다. 단, 회사의 고의 또는 중과실의 경우에는 그러하지 아니 합니다.
5. 이용자는 회원가입시 정보는 정확하게 기입하여야 합니다. 주민등록생성기등을 이용하여 허위로 가입을 하거나 주민등록 생성기를 인터넷에 올리거나 이를 이용해 만든 행위에 관하여서도 주민등록법 개정안에 의거하여 엄중히 법적인 제재를 가할 수 있습니다. 또한 이미 제공된 귀하에 대한 정보가 정확한 정보가 되도록 유지, 갱신하여야 하며, 회원은 자신의 캔디바 ID 및 비밀번호를 제3자에게 이용하게 해서는 안됩니다.
6. 이용자는 당사의 사전 승낙없이 서비스를 이용하여 어떠한 영리행위도 할 수 없습니다.
7. 이용자는 당사 서비스를 이용하여 얻은 정보를 당사의 사전승낙 없이 복사, 복제, 변경, 번역, 출판·방송 기타의 방법으로 사용하거나 이를 타인에게 제공할 수 없습니다.
8. 이용자는 당사 서비스 이용과 관련하여 다음 각 호의 행위를 하여서는 안됩니다.
1) 다른 회원의 캔디바 ID를 부정 사용하는 행위
2) 범죄행위를 목적으로 하거나 기타 범죄행위와 관련된 행위
3) 선량한 풍속, 기타 사회질서를 해하는 행위
4) 타인의 명예를 훼손하거나 모욕하는 행위
5) 타인의 지적재산권 등의 권리를 침해하는 행위
6) 해킹행위 또는 컴퓨터 바이러스의 유포행위
7) 타인의 의사에 반하여 광고성 정보 등 일정한 내용을 지속적으로 전송하는 행위
8) 서비스의 안전적인 운영에 지장을 주거나 줄 우려가 있는 일체의 행위
9) 당사 사이트에 게시된 정보의 변경
10) 기타 전기통신법 제53조와 전기통신사업법 시행령 16조(불온통신), 통신사업법 제53조3항에 위배되는 행위
9. 다른 이용자들에게 피해를 주는 다음 각 호에 해당하는 행위를 하여서는 안됩니다.
1) 회사가 인정하는 서비스의 공식 운영자인 아이디를 사칭하는 내용
2) 선정적이고 음란한 내용
3) 반사회적이고 관계법령에 저촉되는 내용
4) 기타 제3자의 상표권, 저작권에 위배될 가능성이 있는 내용
5) 비어, 속어라고 판단되는 내용
6) 욕설이나 노골적인 성 묘사를 하는 내용
7) 다른 이용자를 희롱하거나, 위협하거나, 특정 이용자에게 지속적으로 고통을 주거나,불편을 주는 행위
8) 서비스 내 또는 웹사이트 상에서 다른 회사 제품의 광고나 판촉활동을 하는 행위
9) 저작권자의 허가 없이 저작권 문제가 발생할 소지가 있는 내용을 서비스 내 또는 웹사이트 상에 배포하는 행위
제 4장 서비스 이용·제한·정지등
제 12조 (서비스의 이용)
1. 캔디바 서비스의 이용은 기본적으로 무료입니다. 단 회사에서 정한 별도의 유효정보 및 유료서비스에 대해서는 그러지 아니하며, 유료서비스 이용에 관한 사항은 회사가 별도로 정한 약관 및 정책에 따릅니다.
2. 유료 서비스 이용의 결제에 관한 사항(충전방법, 사용, 환불 등)은 바(Bar)정책 이용약관에 따릅니다.
3. 아바타 및 게임 아이템 등 유료서비스의 소유권은 회사에 있으며, 웹상의 사용권은 해당 컨텐츠를 구매한 이용자에게 있습니다.
제 13조 (서비스 이용시간 및 중지)
1. 서비스 이용은 회사의 업무상 또는 기술상 특별한 지장이 없는 한 연중 무휴, 1일 24시간을 원칙으로 합니다. 다만, 회사는 서비스의 데이터 베이스별로 이용가능시간을 정할 수 있으며, 이 경우 그 내용은 회사가 정하여 서비스에 게시하거나 별도로 공시하는 바에 따릅니다.
1) 제1항의 규정에도 불구하고 정기점검 등의 필요로 회사가 정한 날 또는 시간은 예외적으로 서비스 이용을 제한할 수 있습니다.
2. 전시,사변,천재지변 또는 이에 준하는 국가비상사태가 발생하거나 발생할 우려가 있는 경우와 전기통신사업법에 의한 기간통신사업자가 전기통신서비스를 중지하는등 기타 부득이한 사유가 있는 경우에는 서비스의 전부 또는 일부를 제한하거나 정지할 수 있습니다.
1) 회사는 제1항의 규정에 의하여 서비스의 전부 또는 일부를 제한하거나 정지한 때에는 지체없이 이용자에게 알려야 합니다.
3. 이용자가 국익 또는 사회적 공익을 저해할 목적이나 범죄적 목적으로 서비스를 이용하고 있다고 판단되는 경우에 회사는 이용자에게 사전 통보 없이 서비스를 중단할 수 있으며 그에 따른 데이터도 복구를 전제로 하지않고 삭제할 수 있습니다.
4. 회사는 이용정지 기간 중에 사유가 해소된 것이 확인된 경우에는 지체없이 서비스를 재개통 또는 이용정지를 해제합니다.
제 14조 (각종 자료의 저장기간)
회사는 필요한 경우 서비스의 일정 부분별로 이용자가 게시한 자료나 이용자의 필요에 의해 저장하고 있는 자료에 대해 일정한 게재기간 또는 저장기간을 정할 수 있으며, 필요에 따라 기간을 변경할 수 있습니다.
제 15조 (게시물의 저작권)
1. 이용자가 서비스 홈페이지에 게시하거나 등록한 자료의 지적재산권은 이용자에게 귀속됩니다. 단, 회사는 서비스 홈페이지의 게재권을 가지며 비상업적 목적으로는 이용자의 게시물을 활용할 수 있습니다.
2. 이용자는 서비스를 이용하여 얻은 정보를 가공, 판매하는 행위 등 게재 된 자료를 상업적으로 이용할 수 없으며 이를 위반하여 발생하는 제반 문제에 대한 책임은 이용자에게 있습니다.
3. 회사는 이용자 게시물의 내용 검열, 검색 및 관리에 따른 일체의 손해배상 책임을 지지아니 합니다.
제 5장 개인정보 보호
제 16조 개인정보보호
회사는 관계법령이 정하는 바에 따라 회원등록정보를 포함한 회원의 개인정보를 보호하기 위하여 노력을 합니다. 회원의 개인정보보호에 관하여 관계법령 및 회사가 정하는 개인정보보호정책에 정한 바에 따릅니다.
제 6장 계약해지 및 이용제한
제 17조 (계약의 해지)
회원이 이용계약을 해지하고자 하는 경우에는 회원 본인이 온라인을 통하여 등록해지신청을 하여야 합니다.
제 18조 (이용제한)
1. 회사는 회원이 다음 각 호의 사유에 해당하는 경우 사전통지 없이 회원의 서비스 이용제한 및 적법한 조치를 취할 수 있으며 이용계약을 해지하거나 또는 기간을 정하여 서비스를 중지할 수 있습니다.
1) 회원 가입신청 또는 변경시 허위 내용을 등록한 경우
2) 타인의 서비스 이용을 방해하거나 그 정보를 도용한 경우
3) 회사의 운영진, 직원 또는 관계자를 사칭하는 경우
4) 회사의 사전승락없이 서비스를 이용하여 영업활동을 하는 경우
5) 회원ID를 타인과 거래하거나 회원ID의 게임상 사이버 자산을 타인과 매매하는 행위를 하는 경우
6) 회사 프로그램상의 버그를 악용하여 정상적이지 아니한 방법으로 게임상 사이버자산을 취득하는 행위를 하는 경우
7) 서비스를 통하여 얻은 정보를 회사의 사전승낙없이 서비스 이용 외이 목적으로 복제하거나 이를 출판 및 방송 등에 사용하거나, 제3자에게 제공하는 경우
8) 회사 또는 제3자의 저작권 등 기타 지적재산권을 침해하는 내용을 전송, 게시, 전자우편 또는 기타의 방법으로 타인에게 유포하는 경우
9) 공공질서 및 미풍약속에 위반되는 음란한 내용의 정보, 문장, 도형, 음향, 동영상을 전송, 게시, 전자우편 또는 기타의 방법으로 타인에게 유포하는 경우
10) 심히 모욕적이거나 개인신상에 대한 내용이어서 타인의 명예나 프라이버시를 침해할 수 있는 내용을 전송, 게시, 전자우편 또는 기타의 방법으로 타인에게 유포하는 경우
11) 서비스에 위해를 가하거나 고의로 방해한 경우
12) 다른 회원을 희롱, 위협하거나 특정 이용자에게 지속적으로 고통, 불편을 주는 행위를 하는 경우
13) 범죄와 관련이 있다고 객관적으로 판단되는 행위를 하는 경우
14) 본 서비스를 이용함에 있어 본 약관 및 기타 회사가 정한 정책 또한 운영 규칙을 위반하는 경우
15) 기타 관련 법령에 위배하는 행위를 하는 경우
제 7장 분쟁조정 및 기타사항
제 19조 (손해배상)
회사는 무료로 제공되는 서비스 이용과 관련하여 회원에게 발생한 어떠한 손해에 관하여도 책임을 지지 않습니다.
제 20조 (면책조항)
1. 회사는 천재지변 또는 이에 준하는 불가항력으로 인하여 서비스를 제공 할 수 없는 경우에는 서비스 제공에 관한 책임이 면제됩니다.
2. 회사는 회원의 귀책사유로 인한 서비스 이용의 장애에 대하여 책임을 지지 않습니다.
3. 회사는 회원이 서비스를 이용하여 기대하는 수익을 상실한 것이나 서비스를 통하여 얻은 자료로 인한 손해에 관하여 책임을 지지 않습니다.
4. 회사는 회원이 서비스에 게재한 정보, 자료, 사실의 신뢰도, 정확성 등 내용에 관하여는 책임을 지지 않습니다.
5. 회사는 서비스 이용과 관련하여 가입자에게 발생한 손해 가운데 가입자의 고의, 과실에 의한 손해에 대하여 책임을 지지 않습니다.
6. 회사는 회원간 또는 회원과 제3자간에 서비스를 매개로 하여 물품거래 혹은 금전적 거래등과 관련하여 어떠한 책임도 부담하지 않습니다.
제 21조(분쟁조정)
회사와 이용고객은 개인정보에 관한 분쟁이 있는 경우 신속하고 효과적인 분쟁해결을 위하여 한국정보보호진흥원내의 개인정보분쟁조정위원회에 그 처리를 의뢰할 수 있습니다.
제 22조(회사의 소유권)
1. 회사의 서비스, 소프트웨어, 이미지, 마크, 로고, 디자인, 서비스명칭, 정보 및 상표 등과 관련된 지적재산권 및 기타 권리는 회사에게 소유권이 있습니다.
2. 이용자는 회사가 명시적으로 승인한 경우를 제외하고는 전항의 소정의 각 재산에 대한 전부 또는 일부의 수정, 대여, 대출, 판매, 배포, 제 작, 양도, 재라이센스, 담보권 설정 행위, 상업적 이용 행위를 할 수 없으며, 제3자로 하여금 이와 같은 행위를 하도록 허락할 수 없습니다.
Binary file not shown.
+230
View File
@@ -0,0 +1,230 @@
<?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
*/
namespace core;
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() {
}
/**
* Query wrapper that calls query_start() and query_end()
* @param string $sql
* @param array|null $params
* @param int $querytype
* @param ?callable $callback
* @return string $handle handle property
*/
public function with_query_start_end($sql, ?array $params, $querytype, $callback = null) {
$this->query_start($sql, $params, $querytype);
$ret = $this->handle;
if ($callback) {
call_user_func($callback, $ret);
}
$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): void {
$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;
}
}
@@ -0,0 +1,66 @@
<?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
*/
namespace core;
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;
}
}
@@ -0,0 +1,43 @@
<?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
*/
namespace core;
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;
}
@@ -0,0 +1,134 @@
<?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
*/
namespace core;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/read_slave_moodle_database.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 read_slave_moodle_database::get_records_sql()
* For the tests where we need both fake result and dbhandle info.
* @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_p($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
return parent::get_records_sql($sql, $params);
}
/**
* 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;
}
}
/**
* 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(): \stdClass {
return new \stdClass();
}
/**
* Iterator interface
* @return void
*/
public function next(): void {
}
/**
* Iterator interface
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key() {
}
/**
* Iterator interface
* @return bool
*/
public function valid(): bool {
return false;
}
}
@@ -0,0 +1,54 @@
<?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
*/
namespace core;
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);
}
}
@@ -0,0 +1,70 @@
<?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(): stdClass {
return new stdClass();
}
/**
* Iterator interface
* @return void
*/
public function next(): void {
}
/**
* Iterator interface
* @return mixed
*/
#[\ReturnTypeWillChange]
public function key() {
}
/**
* Iterator interface
* @return bool
*/
public function valid(): bool {
return false;
}
}
@@ -0,0 +1,94 @@
<?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 debugging fixture
*
* @package core
* @category dml
* @copyright 2020 Brendan Heywood <brendan@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Test SQL debugging fixture
*
* @package core
* @category dml
* @copyright 2020 Brendan Heywood <brendan@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_dml_sql_debugging_fixture {
/** @var db handle */
private $db;
/**
* constructor
* @param testcase $testcase test object
*/
public function __construct($testcase) {
$this->db = $testcase->getMockBuilder(\moodle_database::class)
->getMockForAbstractClass();
}
/**
* Get db handle
* @return a db handle
*/
public function get_mock() {
return $this->db;
}
/**
* Test caller in stacktrace
* @param string $sql original sql
* @return string sql with comments
*/
public function one(string $sql) {
$method = new \ReflectionMethod($this->db, 'add_sql_debugging');
return $method->invoke($this->db, $sql);
}
/**
* Test caller in stacktrace
* @param string $sql original sql
* @return string sql with comments
*/
public function two(string $sql) {
return $this->one($sql);
}
/**
* Test caller in stacktrace
* @param string $sql original sql
* @return string sql with comments
*/
public function three(string $sql) {
return $this->two($sql);
}
/**
* Test caller in stacktrace
* @param string $sql original sql
* @return string sql with comments
*/
public function four(string $sql) {
return $this->three($sql);
}
}
+401
View File
@@ -0,0 +1,401 @@
<?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
*/
namespace core;
use Exception;
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 object|array $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 $arr,...
* @throws Exception
*/
public function sql_concat(...$arr) {
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
*
* @param string $field
* @param string $separator
* @param string $sort
* @return string
* @throws Exception
*/
public function sql_group_concat(string $field, string $separator = ', ', string $sort = ''): string {
throw new Exception('sql_group_concat() 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;
}
}
+125
View File
@@ -0,0 +1,125 @@
<?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
*/
namespace core;
use ReflectionProperty;
/**
* 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 {
/**
* Constructs a mock db driver
*
* @param bool $external
*/
public function __construct($external = false) {
parent::__construct($external);
$rw = fopen("php://memory", 'r+');
fputs($rw, 'rw');
$ro = fopen("php://memory", 'r+');
fputs($ro, 'ro');
$this->prefix = 'test_'; // Default, not to leave empty.
$rcp = new ReflectionProperty(parent::class, 'wantreadslave');
$rcp->setValue($this, true);
$this->dbhwrite = $rw;
$this->dbhreadonly = $ro;
$this->set_db_handle($this->dbhwrite);
$this->temptables = new \moodle_temptables($this);
}
/**
* Check db handle
* @param string $id
* @return bool
*/
public function db_handle_is($id) {
$dbh = $this->get_db_handle();
rewind($dbh);
return stream_get_contents($dbh) == $id;
}
/**
* Check db handle is rw
* @return bool
*/
public function db_handle_is_rw() {
return $this->db_handle_is('rw');
}
/**
* Check db handle is ro
* @return bool
*/
public function db_handle_is_ro() {
return $this->db_handle_is('ro');
}
/**
* Upgrade to public
* @return resource
*/
public function get_db_handle() {
return parent::get_db_handle();
}
/**
* Upgrade to public
* @param string $sql
* @param array|null $params
* @param int $type
* @param array $extrainfo
*/
public function query_start($sql, ?array $params, $type, $extrainfo = null) {
return parent::query_start($sql, $params, $type);
}
/**
* Upgrade to public
* @param mixed $result
*/
public function query_end($result) {
parent::query_end($result);
$this->set_db_handle($this->dbhwrite);
}
/**
* Upgrade to public
*/
public function dispose() {
}
}
+122
View File
@@ -0,0 +1,122 @@
<?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
*/
namespace core;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/../../../ddl/sql_generator.php');
use xmldb_table;
use xmldb_field;
/**
* 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 {
// phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod
/**
* 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) {
return [];
}
/**
* 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) {
return [];
}
/**
* 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) {
return '';
}
/**
* 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) {
return [];
}
/**
* 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) {
return [];
}
/**
* 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) {
return [];
}
/**
* Returns an array of reserved words (lowercase) for this DB
* @return array An array of database specific reserved words
*/
public static function getReservedWords() {
return [];
}
}
@@ -0,0 +1,141 @@
<?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/>.
namespace core;
use ReflectionClass;
use mysqli;
use moodle_database, mysqli_native_moodle_database;
use moodle_exception;
/**
* Test specific features of the MySql dml.
*
* @package core
* @category test
* @copyright 2023 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mysqli_native_moodle_database
*/
class mysqli_native_moodle_database_test extends \advanced_testcase {
/**
* Set up.
*/
public function setUp(): void {
global $DB;
parent::setUp();
// Skip tests if not using Postgres.
if (!($DB instanceof mysqli_native_moodle_database)) {
$this->markTestSkipped('MySql-only test');
}
}
/**
* SSL connection helper.
*
* @param bool|null $compress
* @param string|null $ssl
* @return mysqli
* @throws moodle_exception
*/
public function new_connection(?bool $compress = false, ?string $ssl = null): mysqli {
global $DB;
// Open new connection.
$cfg = $DB->export_dbconfig();
if (!isset($cfg->dboptions)) {
$cfg->dboptions = [];
}
$cfg->dboptions['clientcompress'] = $compress;
$cfg->dboptions['ssl'] = $ssl;
// Get a separate disposable db connection handle with guaranteed 'readonly' config.
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
$db2->raw_connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
$reflector = new ReflectionClass($db2);
$rp = $reflector->getProperty('mysqli');
return $rp->getValue($db2);
}
/**
* Test client compression helper.
*
* @param mysqli $mysqli
* @return array
*/
public function connection_status($mysqli): array {
$mysqli->query("SELECT * FROM INFORMATION_SCHEMA.TABLES");
$stats = [];
foreach ($mysqli->query('SHOW SESSION STATUS')->fetch_all(MYSQLI_ASSOC) as $r) {
$stats[$r['Variable_name']] = $r['Value'];
}
return $stats;
}
/**
* Test client compression.
*
* @return void
*/
public function test_client_compression(): void {
$mysqli = $this->new_connection();
$stats = $this->connection_status($mysqli);
$this->assertEquals('OFF', $stats['Compression']);
$sent = $stats['Bytes_sent'];
$mysqlic = $this->new_connection(true);
$stats = $this->connection_status($mysqlic);
$this->assertEquals('ON', $stats['Compression']);
$sentc = $stats['Bytes_sent'];
$this->assertLessThan($sent, $sentc);
}
/**
* Test SSL connection.
*
* Well as much as we can, mysqli does not reliably report connect errors.
* @return void
*/
public function test_ssl_connection(): void {
try {
$mysqli = $this->new_connection(false, 'require');
// Either connect ...
$this->assertNotNull($mysqli);
} catch (moodle_exception $e) {
// ... or fail.
// Unfortunately we cannot be sure with the error string.
$this->markTestSkipped('MySQL server does not support SSL. Unable to complete the test.');
return;
}
try {
$mysqli = $this->new_connection(false, 'verify-full');
// Either connect ...
$this->assertNotNull($mysqli);
} catch (moodle_exception $e) {
// ... or fail with invalid cert.
// Same as above, but we cannot really expect properly signed cert, so ignore.
}
$this->expectException(moodle_exception::class);
$this->new_connection(false, 'invalid-mode');
}
}
@@ -0,0 +1,429 @@
<?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 specific features of the Postgres dml.
*
* @package core
* @category test
* @copyright 2020 Ruslan Kabalin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core;
use stdClass, ReflectionClass;
use moodle_database, pgsql_native_moodle_database;
use xmldb_table;
use moodle_exception;
/**
* Test specific features of the Postgres dml.
*
* @package core
* @category test
* @copyright 2020 Ruslan Kabalin
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \pgsql_native_moodle_database
*/
class pgsql_native_moodle_database_test extends \advanced_testcase {
/**
* Setup before class.
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->libdir.'/dml/pgsql_native_moodle_database.php');
}
/**
* Set up.
*/
public function setUp(): void {
global $DB;
parent::setUp();
// Skip tests if not using Postgres.
if (!($DB instanceof pgsql_native_moodle_database)) {
$this->markTestSkipped('Postgres-only test');
}
}
/**
* Get a xmldb_table object for testing, deleting any existing table
* of the same name, for example if one was left over from a previous test
* run that crashed.
*
* @param string $suffix table name suffix, use if you need more test tables
* @return xmldb_table the table object.
*/
private function get_test_table($suffix = ''): xmldb_table {
$tablename = "test_table";
if ($suffix !== '') {
$tablename .= $suffix;
}
$table = new xmldb_table($tablename);
$table->setComment("This is a test'n drop table. You can drop it safely");
return $table;
}
/**
* Find out the current index used for unique SQL_PARAMS_NAMED.
*
* @return int
*/
private function get_current_index(): int {
global $DB;
$reflector = new ReflectionClass($DB);
$property = $reflector->getProperty('inorequaluniqueindex');
return (int) $property->getValue($DB);
}
public function test_get_in_or_equal_below_limit(): void {
global $DB;
// Just less than 65535 values, expect fallback to parent method.
$invalues = range(1, 65533);
list($usql, $params) = $DB->get_in_or_equal($invalues);
$this->assertSame('IN ('.implode(',', array_fill(0, count($invalues), '?')).')', $usql);
$this->assertEquals(count($invalues), count($params));
foreach ($params as $key => $value) {
$this->assertSame($invalues[$key], $value);
}
}
public function test_get_in_or_equal_single_array_value(): void {
global $DB;
// Single value (in an array), expect fallback to parent method.
$invalues = array('value1');
list($usql, $params) = $DB->get_in_or_equal($invalues);
$this->assertEquals("= ?", $usql);
$this->assertCount(1, $params);
$this->assertEquals($invalues[0], $params[0]);
}
public function test_get_in_or_equal_single_scalar_value(): void {
global $DB;
// Single value (scalar), expect fallback to parent method.
$invalue = 'value1';
list($usql, $params) = $DB->get_in_or_equal($invalue);
$this->assertEquals("= ?", $usql);
$this->assertCount(1, $params);
$this->assertEquals($invalue, $params[0]);
}
public function test_get_in_or_equal_multiple_int_value(): void {
global $DB;
// 65535 values, int.
$invalues = range(1, 65535);
list($usql, $params) = $DB->get_in_or_equal($invalues);
$this->assertSame('IN (VALUES ('.implode('),(', array_fill(0, count($invalues), '?::bigint')).'))', $usql);
$this->assertEquals($params, $invalues);
}
public function test_get_in_or_equal_multiple_int_value_not_equal(): void {
global $DB;
// 65535 values, not equal, int.
$invalues = range(1, 65535);
list($usql, $params) = $DB->get_in_or_equal($invalues, SQL_PARAMS_QM, 'param', false);
$this->assertSame('NOT IN (VALUES ('.implode('),(', array_fill(0, count($invalues), '?::bigint')).'))', $usql);
$this->assertEquals($params, $invalues);
}
public function test_get_in_or_equal_named_int_value_default_name(): void {
global $DB;
// 65535 values, int, SQL_PARAMS_NAMED.
$index = $this->get_current_index();
$invalues = range(1, 65535);
list($usql, $params) = $DB->get_in_or_equal($invalues, SQL_PARAMS_NAMED);
$regex = '/^'.
preg_quote('IN (VALUES (:param'.$index.'::bigint),(:param'.++$index.'::bigint),(:param'.++$index.'::bigint)').'/';
$this->assertMatchesRegularExpression($regex, $usql);
foreach ($params as $value) {
$this->assertEquals(current($invalues), $value);
next($invalues);
}
}
public function test_get_in_or_equal_named_int_value_specified_name(): void {
global $DB;
// 65535 values, int, SQL_PARAMS_NAMED, define param name.
$index = $this->get_current_index();
$invalues = range(1, 65535);
list($usql, $params) = $DB->get_in_or_equal($invalues, SQL_PARAMS_NAMED, 'ppp');
// We are in same DBI instance, expect uniqie param indexes.
$regex = '/^'.
preg_quote('IN (VALUES (:ppp'.$index.'::bigint),(:ppp'.++$index.'::bigint),(:ppp'.++$index.'::bigint)').'/';
$this->assertMatchesRegularExpression($regex, $usql);
foreach ($params as $value) {
$this->assertEquals(current($invalues), $value);
next($invalues);
}
}
public function test_get_in_or_equal_named_scalar_value_specified_name(): void {
global $DB;
// 65535 values, string.
$invalues = array_fill(1, 65535, 'abc');
list($usql, $params) = $DB->get_in_or_equal($invalues);
$this->assertMatchesRegularExpression('/^' . preg_quote('IN (VALUES (?::text),(?::text),(?::text)') . '/', $usql);
foreach ($params as $value) {
$this->assertEquals(current($invalues), $value);
next($invalues);
}
}
public function test_get_in_or_equal_query_use(): void {
global $DB;
$this->resetAfterTest();
$dbman = $DB->get_manager();
$table = $this->get_test_table();
$tablename = $table->getName();
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_field('content', XMLDB_TYPE_TEXT, 'big', null, XMLDB_NOTNULL);
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$dbman->create_table($table);
$rec1 = ['course' => 3, 'content' => 'hello', 'name' => 'xyz'];
$DB->insert_record($tablename, $rec1);
$rec2 = ['course' => 3, 'content' => 'world', 'name' => 'abc'];
$DB->insert_record($tablename, $rec2);
$rec3 = ['course' => 5, 'content' => 'hello', 'name' => 'xyz'];
$DB->insert_record($tablename, $rec3);
$rec4 = ['course' => 6, 'content' => 'universe'];
$DB->insert_record($tablename, $rec4);
$currentcount = $DB->count_records($tablename);
// Getting all 4.
$values = range(1, 65535);
list($insql, $inparams) = $DB->get_in_or_equal($values);
$sql = "SELECT *
FROM {{$tablename}}
WHERE id $insql
ORDER BY id ASC";
$this->assertCount($currentcount, $DB->get_records_sql($sql, $inparams));
// Getting 'hello' records (text).
$values = array_fill(1, 65535, 'hello');
list($insql, $inparams) = $DB->get_in_or_equal($values);
$sql = "SELECT *
FROM {{$tablename}}
WHERE content $insql
ORDER BY id ASC";
$result = $DB->get_records_sql($sql, $inparams);
$this->assertCount(2, $result);
$this->assertEquals([1, 3], array_keys($result));
// Getting NOT 'hello' records (text).
$values = array_fill(1, 65535, 'hello');
list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_QM, 'param', false);
$sql = "SELECT *
FROM {{$tablename}}
WHERE content $insql
ORDER BY id ASC";
$result = $DB->get_records_sql($sql, $inparams);
$this->assertCount(2, $result);
$this->assertEquals([2, 4], array_keys($result));
// Getting 'xyz' records (char and NULL mix).
$values = array_fill(1, 65535, 'xyz');
list($insql, $inparams) = $DB->get_in_or_equal($values);
$sql = "SELECT *
FROM {{$tablename}}
WHERE name $insql
ORDER BY id ASC";
$result = $DB->get_records_sql($sql, $inparams);
$this->assertCount(2, $result);
$this->assertEquals([1, 3], array_keys($result));
// Getting NOT 'xyz' records (char and NULL mix).
$values = array_fill(1, 65535, 'xyz');
list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_QM, 'param', false);
$sql = "SELECT *
FROM {{$tablename}}
WHERE name $insql
ORDER BY id ASC";
$result = $DB->get_records_sql($sql, $inparams);
// NULL will not be in result.
$this->assertCount(1, $result);
$this->assertEquals([2], array_keys($result));
// Getting numbeic records.
$values = array_fill(1, 65535, 3);
list($insql, $inparams) = $DB->get_in_or_equal($values);
$sql = "SELECT *
FROM {{$tablename}}
WHERE course $insql
ORDER BY id ASC";
$result = $DB->get_records_sql($sql, $inparams);
$this->assertCount(2, $result);
$this->assertEquals([1, 2], array_keys($result));
// Getting numbeic records with NOT condition.
$values = array_fill(1, 65535, 3);
list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_QM, 'param', false);
$sql = "SELECT *
FROM {{$tablename}}
WHERE course $insql
ORDER BY id ASC";
$result = $DB->get_records_sql($sql, $inparams);
$this->assertCount(2, $result);
$this->assertEquals([3, 4], array_keys($result));
}
public function test_get_in_or_equal_big_table_query(): void {
global $DB;
$this->resetAfterTest();
$dbman = $DB->get_manager();
$table = $this->get_test_table();
$tablename = $table->getName();
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('oneint', XMLDB_TYPE_INTEGER, '10', null, null, null, 100);
$table->add_field('onenum', XMLDB_TYPE_NUMBER, '10,2', null, null, null, 200);
$table->add_field('onechar', XMLDB_TYPE_CHAR, '100', null, null, null, 'onestring');
$table->add_field('onetext', XMLDB_TYPE_TEXT, 'big', null, null, null);
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$dbman->create_table($table);
$record = new stdClass();
$record->course = 1;
$record->oneint = null;
$record->onenum = 1.0;
$record->onechar = 'a';
$record->onetext = 'aaa';
$records = [];
for ($i = 1; $i <= 65535; $i++) {
$rec = clone($record);
$rec->oneint = $i;
$records[$i] = $rec;
}
// Populate table with 65535 records.
$DB->insert_records($tablename, $records);
// And one more record.
$record->oneint = -1;
$DB->insert_record($tablename, $record);
// Check we can fetch all.
$values = range(1, 65535);
list($insql, $inparams) = $DB->get_in_or_equal($values);
$sql = "SELECT *
FROM {{$tablename}}
WHERE oneint $insql
ORDER BY id ASC";
$stored = $DB->get_records_sql($sql, $inparams);
// Check we got correct set of records.
$this->assertCount(65535, $stored);
$oneint = array_column($stored, 'oneint');
$this->assertEquals($values, $oneint);
// Check we can fetch all, SQL_PARAMS_NAMED.
$values = range(1, 65535);
list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_NAMED);
$sql = "SELECT *
FROM {{$tablename}}
WHERE oneint $insql
ORDER BY id ASC";
$stored = $DB->get_records_sql($sql, $inparams);
// Check we got correct set of records.
$this->assertCount(65535, $stored);
$oneint = array_column($stored, 'oneint');
$this->assertEquals($values, $oneint);
// Check we can fetch one using NOT IN.
list($insql, $inparams) = $DB->get_in_or_equal($values, SQL_PARAMS_QM, 'param', false);
$sql = "SELECT *
FROM {{$tablename}}
WHERE oneint $insql
ORDER BY id ASC";
$stored = $DB->get_records_sql($sql, $inparams);
// Check we got correct set of records.
$this->assertCount(1, $stored);
$oneint = array_column($stored, 'oneint');
$this->assertEquals([-1], $oneint);
}
/**
* SSL connection helper.
*
* @param mixed $ssl
* @return resource|PgSql\Connection
* @throws moodle_exception
*/
public function new_connection($ssl) {
global $DB;
// Open new connection.
$cfg = $DB->export_dbconfig();
if (!isset($cfg->dboptions)) {
$cfg->dboptions = [];
}
$cfg->dboptions['ssl'] = $ssl;
// Get a separate disposable db connection handle with guaranteed 'readonly' config.
$db2 = moodle_database::get_driver_instance($cfg->dbtype, $cfg->dblibrary);
$db2->raw_connect($cfg->dbhost, $cfg->dbuser, $cfg->dbpass, $cfg->dbname, $cfg->prefix, $cfg->dboptions);
$reflector = new ReflectionClass($db2);
$rp = $reflector->getProperty('pgsql');
return $rp->getValue($db2);
}
/**
* Test SSL connection.
*
* @return void
* @covers ::raw_connect
*/
public function test_ssl_connection(): void {
$pgconnerr = 'pg_connect(): Unable to connect to PostgreSQL server:';
try {
$pgsql = $this->new_connection('require');
// Either connect ...
$this->assertNotNull($pgsql);
} catch (moodle_exception $e) {
// ... or fail with SSL not supported.
$this->assertStringContainsString($pgconnerr, $e->debuginfo);
$this->assertStringContainsString('server does not support SSL', $e->debuginfo);
$this->markTestSkipped('Postgres server does not support SSL. Unable to complete the test.');
return;
}
try {
$pgsql = $this->new_connection('verify-full');
// Either connect ...
$this->assertNotNull($pgsql);
} catch (moodle_exception $e) {
// ... or fail with invalid cert.
$this->assertStringContainsString($pgconnerr, $e->debuginfo);
$this->assertStringContainsString('change sslmode to disable server certificate verification', $e->debuginfo);
}
$this->expectException(moodle_exception::class);
$this->new_connection('invalid-mode');
}
}
@@ -0,0 +1,433 @@
<?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 specific features of the Postgres dml support relating to recordsets.
*
* @package core
* @category test
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/lib/dml/pgsql_native_moodle_database.php');
/**
* Test specific features of the Postgres dml support relating to recordsets.
*
* @package core
* @category test
* @copyright 2017 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class pgsql_native_recordset_test extends basic_testcase {
/** @var pgsql_native_moodle_database Special database connection */
protected $specialdb;
/**
* Creates a second db connection and a temp table with values in for testing.
*/
protected function setUp(): void {
global $DB;
parent::setUp();
// Skip tests if not using Postgres.
if (!($DB instanceof pgsql_native_moodle_database)) {
$this->markTestSkipped('Postgres-only test');
}
}
/**
* Initialises database connection with given fetch buffer size
* @param int $fetchbuffersize Size of fetch buffer
*/
protected function init_db($fetchbuffersize) {
global $CFG, $DB;
// To make testing easier, create a database with the same dboptions as the real one,
// but a low number for the cursor size.
$this->specialdb = \moodle_database::get_driver_instance('pgsql', 'native', true);
$dboptions = $CFG->dboptions;
$dboptions['fetchbuffersize'] = $fetchbuffersize;
$this->specialdb->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname,
$DB->get_prefix(), $dboptions);
// Create a temp table.
$dbman = $this->specialdb->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);
// Add some records to the table.
for ($index = 1; $index <= 7; $index++) {
$this->specialdb->insert_record('silly_test_table', ['msg' => 'record' . $index]);
}
}
/**
* Gets rid of the second db connection.
*/
protected function tearDown(): void {
if ($this->specialdb) {
$table = new xmldb_table('silly_test_table');
$this->specialdb->get_manager()->drop_table($table);
$this->specialdb->dispose();
$this->specialdb = null;
}
parent::tearDown();
}
/**
* Tests that get_recordset_sql works when using cursors, which it does when no limit is
* specified.
*/
public function test_recordset_cursors(): void {
$this->init_db(3);
// Query the table and check the actual queries using debug mode, also check the count.
$this->specialdb->set_debug(true);
$before = $this->specialdb->perf_get_queries();
ob_start();
$rs = $this->specialdb->get_recordset_sql('SELECT * FROM {silly_test_table} ORDER BY id');
$index = 0;
foreach ($rs as $rec) {
$index++;
$this->assertEquals('record' . $index, $rec->msg);
}
$this->assertEquals(7, $index);
$rs->close();
$debugging = ob_get_contents();
ob_end_clean();
// Expect 4 fetches - first three, next three, last one (with 2).
$this->assert_query_regexps([
'~SELECT \* FROM~',
'~FETCH 3 FROM crs1~',
'~FETCH 3 FROM crs1~',
'~FETCH 3 FROM crs1~',
'~CLOSE crs1~'], $debugging);
// There should have been 7 queries tracked for perf log.
$this->assertEquals(5, $this->specialdb->perf_get_queries() - $before);
// Try a second time - this time we'll request exactly 3 items so that it has to query
// twice (as it can't tell if the first batch is the last).
$before = $this->specialdb->perf_get_queries();
ob_start();
$rs = $this->specialdb->get_recordset_sql(
'SELECT * FROM {silly_test_table} WHERE id <= ? ORDER BY id', [3]);
$index = 0;
foreach ($rs as $rec) {
$index++;
$this->assertEquals('record' . $index, $rec->msg);
}
$this->assertEquals(3, $index);
$rs->close();
$debugging = ob_get_contents();
ob_end_clean();
$this->specialdb->set_debug(false);
// Expect 2 fetches - first three, then next one (empty).
$this->assert_query_regexps([
'~SELECT \* FROM~',
'~FETCH 3 FROM crs2~',
'~FETCH 3 FROM crs2~',
'~CLOSE crs2~'], $debugging);
// There should have been 4 queries tracked for perf log.
$this->assertEquals(4, $this->specialdb->perf_get_queries() - $before);
}
/**
* Tests that get_recordset_sql works when using cursors and when there are two overlapping
* recordsets being used.
*/
public function test_recordset_cursors_overlapping(): void {
$this->init_db(3);
$rs1 = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$rs2 = $this->specialdb->get_recordset('silly_test_table', null, 'id DESC');
// Read first 3 from first recordset.
$read = [];
$read[] = $rs1->current()->id;
$rs1->next();
$read[] = $rs1->current()->id;
$rs1->next();
$read[] = $rs1->current()->id;
$rs1->next();
$this->assertEquals([1, 2, 3], $read);
// Read 5 from second recordset.
$read = [];
$read[] = $rs2->current()->id;
$rs2->next();
$read[] = $rs2->current()->id;
$rs2->next();
$read[] = $rs2->current()->id;
$rs2->next();
$read[] = $rs2->current()->id;
$rs2->next();
$read[] = $rs2->current()->id;
$rs2->next();
$this->assertEquals([7, 6, 5, 4, 3], $read);
// Now read remainder of first recordset and close it.
$read = [];
$read[] = $rs1->current()->id;
$rs1->next();
$read[] = $rs1->current()->id;
$rs1->next();
$read[] = $rs1->current()->id;
$rs1->next();
$read[] = $rs1->current()->id;
$rs1->next();
$this->assertFalse($rs1->valid());
$this->assertEquals([4, 5, 6, 7], $read);
$rs1->close();
// And remainder of second.
$read = [];
$read[] = $rs2->current()->id;
$rs2->next();
$read[] = $rs2->current()->id;
$rs2->next();
$this->assertFalse($rs2->valid());
$this->assertEquals([2, 1], $read);
$rs2->close();
}
/**
* Tests that get_recordset_sql works when using cursors and transactions inside.
*/
public function test_recordset_cursors_transaction_inside(): void {
$this->init_db(3);
// Transaction inside the recordset processing.
$rs = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$read = [];
foreach ($rs as $rec) {
$read[] = $rec->id;
$transaction = $this->specialdb->start_delegated_transaction();
$transaction->allow_commit();
}
$this->assertEquals([1, 2, 3, 4, 5, 6, 7], $read);
$rs->close();
}
/**
* Tests that get_recordset_sql works when using cursors and a transaction outside.
*/
public function test_recordset_cursors_transaction_outside(): void {
$this->init_db(3);
// Transaction outside the recordset processing.
$transaction = $this->specialdb->start_delegated_transaction();
$rs = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$read = [];
foreach ($rs as $rec) {
$read[] = $rec->id;
}
$this->assertEquals([1, 2, 3, 4, 5, 6, 7], $read);
$rs->close();
$transaction->allow_commit();
}
/**
* Tests that get_recordset_sql works when using cursors and a transaction overlapping.
*/
public function test_recordset_cursors_transaction_overlapping_before(): void {
$this->init_db(3);
// Transaction outside the recordset processing.
$transaction = $this->specialdb->start_delegated_transaction();
$rs = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$transaction->allow_commit();
$read = [];
foreach ($rs as $rec) {
$read[] = $rec->id;
}
$this->assertEquals([1, 2, 3, 4, 5, 6, 7], $read);
$rs->close();
}
/**
* Tests that get_recordset_sql works when using cursors and a transaction overlapping.
*/
public function test_recordset_cursors_transaction_overlapping_after(): void {
$this->init_db(3);
// Transaction outside the recordset processing.
$rs = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$transaction = $this->specialdb->start_delegated_transaction();
$read = [];
foreach ($rs as $rec) {
$read[] = $rec->id;
}
$this->assertEquals([1, 2, 3, 4, 5, 6, 7], $read);
$rs->close();
$transaction->allow_commit();
}
/**
* Tests that get_recordset_sql works when using cursors and a transaction that 'fails' and gets
* rolled back.
*/
public function test_recordset_cursors_transaction_rollback(): void {
$this->init_db(3);
try {
$rs = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$transaction = $this->specialdb->start_delegated_transaction();
$this->specialdb->delete_records('silly_test_table', ['id' => 5]);
$transaction->rollback(new dml_transaction_exception('rollback please'));
$this->fail('should not get here');
} catch (dml_transaction_exception $e) {
$this->assertStringContainsString('rollback please', $e->getMessage());
} finally {
// Rollback should not kill our recordset.
$read = [];
foreach ($rs as $rec) {
$read[] = $rec->id;
}
$this->assertEquals([1, 2, 3, 4, 5, 6, 7], $read);
// This would happen in real code (that isn't within the same function) anyway because
// it would go out of scope.
$rs->close();
}
// OK, transaction aborted, now get the recordset again and check nothing was deleted.
$rs = $this->specialdb->get_recordset('silly_test_table', null, 'id');
$read = [];
foreach ($rs as $rec) {
$read[] = $rec->id;
}
$this->assertEquals([1, 2, 3, 4, 5, 6, 7], $read);
$rs->close();
}
/**
* Tests that get_recordset_sql works when not using cursors, because a limit is specified.
*/
public function test_recordset_no_cursors_limit(): void {
$this->init_db(3);
$this->specialdb->set_debug(true);
$before = $this->specialdb->perf_get_queries();
ob_start();
$rs = $this->specialdb->get_recordset_sql(
'SELECT * FROM {silly_test_table} ORDER BY id', [], 0, 100);
$index = 0;
foreach ($rs as $rec) {
$index++;
$this->assertEquals('record' . $index, $rec->msg);
}
$this->assertEquals(7, $index);
$rs->close();
$this->specialdb->set_debug(false);
$debugging = ob_get_contents();
ob_end_clean();
// Expect direct request without using cursors.
$this->assert_query_regexps(['~SELECT \* FROM~'], $debugging);
// There should have been 1 query tracked for perf log.
$this->assertEquals(1, $this->specialdb->perf_get_queries() - $before);
}
/**
* Tests that get_recordset_sql works when not using cursors, because the config setting turns
* them off.
*/
public function test_recordset_no_cursors_config(): void {
$this->init_db(0);
$this->specialdb->set_debug(true);
$before = $this->specialdb->perf_get_queries();
ob_start();
$rs = $this->specialdb->get_recordset_sql('SELECT * FROM {silly_test_table} ORDER BY id');
$index = 0;
foreach ($rs as $rec) {
$index++;
$this->assertEquals('record' . $index, $rec->msg);
}
$this->assertEquals(7, $index);
$rs->close();
$this->specialdb->set_debug(false);
$debugging = ob_get_contents();
ob_end_clean();
// Expect direct request without using cursors.
$this->assert_query_regexps(['~SELECT \* FROM~'], $debugging);
// There should have been 1 query tracked for perf log.
$this->assertEquals(1, $this->specialdb->perf_get_queries() - $before);
}
/**
* Asserts that database debugging output matches the expected list of SQL queries, specified
* as an array of regular expressions.
*
* @param string[] $expected Expected regular expressions
* @param string $debugging Debugging text from the database
*/
protected function assert_query_regexps(array $expected, $debugging) {
$lines = explode("\n", $debugging);
$index = 0;
$params = false;
foreach ($lines as $line) {
if ($params) {
if ($line === ')]') {
$params = false;
}
continue;
}
// Skip irrelevant lines.
if (preg_match('~^---~', $line)) {
continue;
}
if (preg_match('~^Query took~', $line)) {
continue;
}
if (trim($line) === '') {
continue;
}
// Skip param lines.
if ($line === '[array (') {
$params = true;
continue;
}
if (!array_key_exists($index, $expected)) {
$this->fail('More queries than expected');
}
$this->assertMatchesRegularExpression($expected[$index++], $line);
}
if (array_key_exists($index, $expected)) {
$this->fail('Fewer queries than expected');
}
}
}
+130
View File
@@ -0,0 +1,130 @@
<?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/>.
namespace core;
/**
* Test case for recordset_walk.
*
* @package core
* @category test
* @copyright 2015 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recordset_walk_test extends \advanced_testcase {
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
}
public function test_no_data(): void {
global $DB;
$recordset = $DB->get_recordset('assign');
$walker = new \core\dml\recordset_walk($recordset, array($this, 'simple_callback'));
$this->assertFalse($walker->valid());
$count = 0;
foreach ($walker as $data) {
// No error here.
$count++;
}
$this->assertEquals(0, $count);
$walker->close();
}
public function test_simple_callback(): void {
global $DB;
/** @var \mod_assign_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$courses = array();
for ($i = 0; $i < 10; $i++) {
$courses[$i] = $generator->create_instance(array('course' => SITEID));
}
// Simple iteration.
$recordset = $DB->get_recordset('assign');
$walker = new \core\dml\recordset_walk($recordset, array($this, 'simple_callback'));
$count = 0;
foreach ($walker as $data) {
// Checking that the callback is being executed on each iteration.
$this->assertEquals($data->id . ' potatoes', $data->newfield);
$count++;
}
$this->assertEquals(10, $count);
// No exception if we double-close.
$walker->close();
}
public function test_extra_params_callback(): void {
global $DB;
/** @var \mod_assign_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
$courses = array();
for ($i = 0; $i < 10; $i++) {
$courses[$i] = $generator->create_instance(array('course' => SITEID));
}
// Iteration with extra callback arguments.
$recordset = $DB->get_recordset('assign');
$walker = new \core\dml\recordset_walk(
$recordset,
array($this, 'extra_callback'),
array('brown' => 'onions')
);
$count = 0;
foreach ($walker as $data) {
// Checking that the callback is being executed on each
// iteration and the param is being passed.
$this->assertEquals('onions', $data->brown);
$count++;
}
$this->assertEquals(10, $count);
$walker->close();
}
/**
* Simple callback requiring 1 row fields.
*
* @param \stdClass $data
* @return \Traversable
*/
public function simple_callback($data, $nothing = 'notpassed') {
// Confirm nothing was passed.
$this->assertEquals('notpassed', $nothing);
$data->newfield = $data->id . ' potatoes';
return $data;
}
/**
* Callback requiring 1 row fields + other params.
*
* @param \stdClass $data
* @param mixed $extra
* @return \Traversable
*/
public function extra_callback($data, $extra) {
$data->brown = $extra['brown'];
return $data;
}
}
@@ -0,0 +1,287 @@
<?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 case for sqlsrv dml support.
*
* @package core
* @category test
* @copyright 2017 John Okely
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core;
use sqlsrv_native_moodle_database;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/lib/dml/sqlsrv_native_moodle_database.php');
/**
* Test case for sqlsrv dml support.
*
* @package core
* @category test
* @copyright 2017 John Okely
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sqlsrv_native_moodle_database_test extends \advanced_testcase {
public function setUp(): void {
parent::setUp();
$this->resetAfterTest();
}
/**
* Dataprovider for test_add_no_lock_to_temp_tables
* @return array Data for test_add_no_lock_to_temp_tables
*/
public function add_no_lock_to_temp_tables_provider() {
return [
"Basic temp table, nothing following" => [
'input' => 'SELECT * FROM {table_temp}',
'expected' => 'SELECT * FROM {table_temp} WITH (NOLOCK)'
],
"Basic temp table, with capitalised alias" => [
'input' => 'SELECT * FROM {table_temp} MYTABLE',
'expected' => 'SELECT * FROM {table_temp} MYTABLE WITH (NOLOCK)'
],
"Temp table with alias, and another non-temp table" => [
'input' => 'SELECT * FROM {table_temp} x WHERE y in (SELECT y from {table2})',
'expected' => 'SELECT * FROM {table_temp} x WITH (NOLOCK) WHERE y in (SELECT y from {table2})'
],
"Temp table with reserve word following, no alias" => [
'input' => 'SELECT DISTINCT * FROM {table_temp} WHERE y in (SELECT y from {table2} nottemp)',
'expected' => 'SELECT DISTINCT * FROM {table_temp} WITH (NOLOCK) WHERE y in (SELECT y from {table2} nottemp)'
],
"Temp table with reserve word, lower case" => [
'input' => 'SELECT DISTINCT * FROM {table_temp} where y in (SELECT y from {table2} nottemp)',
'expected' => 'SELECT DISTINCT * FROM {table_temp} WITH (NOLOCK) where y in (SELECT y from {table2} nottemp)'
],
"Another reserve word test" => [
'input' => 'SELECT DISTINCT * FROM {table_temp} PIVOT y in (SELECT y from {table2} nottemp)',
'expected' => 'SELECT DISTINCT * FROM {table_temp} WITH (NOLOCK) PIVOT y in (SELECT y from {table2} nottemp)'
],
"Another reserve word test should fail" => [
'input' => 'SELECT DISTINCT * FROM {table_temp} PIVOT y in (SELECT y from {table2} nottemp)',
'expected' => 'SELECT DISTINCT * FROM {table_temp} WITH (NOLOCK) PIVOT y in (SELECT y from {table2} nottemp)'
],
"Temp table with an alias starting with a keyword" => [
'input' => 'SELECT * FROM {table_temp} asx',
'expected' => 'SELECT * FROM {table_temp} asx WITH (NOLOCK)'
],
"Keep alias with underscore" => [
'input' => 'SELECT * FROM {table_temp} alias_for_table',
'expected' => 'SELECT * FROM {table_temp} alias_for_table WITH (NOLOCK)'
],
"Alias with number" => [
'input' => 'SELECT * FROM {table_temp} a5 WHERE y',
'expected' => 'SELECT * FROM {table_temp} a5 WITH (NOLOCK) WHERE y'
],
"Alias with number and underscore" => [
'input' => 'SELECT * FROM {table_temp} a_5 WHERE y',
'expected' => 'SELECT * FROM {table_temp} a_5 WITH (NOLOCK) WHERE y'
],
"Temp table in subquery" => [
'input' => 'select * FROM (SELECT DISTINCT * FROM {table_temp})',
'expected' => 'select * FROM (SELECT DISTINCT * FROM {table_temp} WITH (NOLOCK))'
],
"Temp table in subquery, with following commands" => [
'input' => 'select * FROM (SELECT DISTINCT * FROM {table_temp} ) WHERE y',
'expected' => 'select * FROM (SELECT DISTINCT * FROM {table_temp} WITH (NOLOCK) ) WHERE y'
],
"Temp table in subquery, with alias" => [
'input' => 'select * FROM (SELECT DISTINCT * FROM {table_temp} x) WHERE y',
'expected' => 'select * FROM (SELECT DISTINCT * FROM {table_temp} x WITH (NOLOCK)) WHERE y'
],
];
}
/**
* Test add_no_lock_to_temp_tables
*
* @param string $input The input SQL query
* @param string $expected The expected resultant query
* @dataProvider add_no_lock_to_temp_tables_provider
*/
public function test_add_no_lock_to_temp_tables($input, $expected): void {
$sqlsrv = new sqlsrv_native_moodle_database();
$reflector = new \ReflectionObject($sqlsrv);
$method = $reflector->getMethod('add_no_lock_to_temp_tables');
$temptablesproperty = $reflector->getProperty('temptables');
$temptables = new temptables_tester();
$temptablesproperty->setValue($sqlsrv, $temptables);
$result = $method->invoke($sqlsrv, $input);
$temptablesproperty->setValue($sqlsrv, null);
$this->assertEquals($expected, $result);
}
/**
* Data provider for test_has_query_order_by
*
* @return array data for test_has_query_order_by
*/
public function has_query_order_by_provider() {
// Fixtures taken from https://docs.moodle.org/en/ad-hoc_contributed_reports.
return [
'User with language => FALSE' => [
'sql' => <<<EOT
SELECT username, lang
FROM prefix_user
EOT
,
'expectedmainquery' => <<<EOT
SELECT username, lang
FROM prefix_user
EOT
,
'expectedresult' => false
],
'List Users with extra info (email) in current course => FALSE' => [
'sql' => <<<EOT
SELECT u.firstname, u.lastname, u.email
FROM prefix_role_assignments AS ra
JOIN prefix_context AS context ON context.id = ra.contextid AND context.contextlevel = 50
JOIN prefix_course AS c ON c.id = context.instanceid AND c.id = %%COURSEID%%
JOIN prefix_user AS u ON u.id = ra.userid
EOT
,
'expectedmainquery' => <<<EOT
SELECT u.firstname, u.lastname, u.email
FROM prefix_role_assignments AS ra
JOIN prefix_context AS context ON context.id = ra.contextid AND context.contextlevel = 50
JOIN prefix_course AS c ON c.id = context.instanceid AND c.id = %%COURSEID%%
JOIN prefix_user AS u ON u.id = ra.userid
EOT
,
'expectedresult' => false
],
'ROW_NUMBER() OVER (ORDER BY ...) => FALSE (https://github.com/jleyva/moodle-block_configurablereports/issues/120)' => [
'sql' => <<<EOT
SELECT COUNT(*) AS 'Users who have logged in today'
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY lastaccess DESC) AS Row
FROM mdl_user
WHERE lastaccess > DATEDIFF(s, '1970-01-01 02:00:00', (SELECT Convert(DateTime, DATEDIFF(DAY, 0, GETDATE()))))
) AS Logins
EOT
,
'expectedmainquery' => <<<EOT
SELECT COUNT() AS 'Users who have logged in today'
FROM () AS Logins
EOT
,
'expectedresult' => false
],
'CONTRIB-7725 workaround) => TRUE' => [
'sql' => <<<EOT
SELECT COUNT(*) AS 'Users who have logged in today'
FROM (
SELECT ROW_NUMBER() OVER(ORDER BY lastaccess DESC) AS Row
FROM mdl_user
WHERE lastaccess > DATEDIFF(s, '1970-01-01 02:00:00', (SELECT Convert(DateTime, DATEDIFF(DAY, 0, GETDATE()))))
) AS Logins ORDER BY 1
EOT
,
'expectedmainquery' => <<<EOT
SELECT COUNT() AS 'Users who have logged in today'
FROM () AS Logins ORDER BY 1
EOT
,
'expectedresult' => true
],
'Enrolment count in each Course => TRUE' => [
'sql' => <<<EOT
SELECT c.fullname, COUNT(ue.id) AS Enroled
FROM prefix_course AS c
JOIN prefix_enrol AS en ON en.courseid = c.id
JOIN prefix_user_enrolments AS ue ON ue.enrolid = en.id
GROUP BY c.id
ORDER BY c.fullname
EOT
,
'expectedmainquery' => <<<EOT
SELECT c.fullname, COUNT() AS Enroled
FROM prefix_course AS c
JOIN prefix_enrol AS en ON en.courseid = c.id
JOIN prefix_user_enrolments AS ue ON ue.enrolid = en.id
GROUP BY c.id
ORDER BY c.fullname
EOT
,
'expectedresult' => true
],
];
}
/**
* Test has_query_order_by
*
* @dataProvider has_query_order_by_provider
* @param string $sql the query
* @param string $expectedmainquery the expected main query
* @param bool $expectedresult the expected result
*/
public function test_has_query_order_by(string $sql, string $expectedmainquery, bool $expectedresult): void {
$mainquery = preg_replace('/\(((?>[^()]+)|(?R))*\)/', '()', $sql);
$this->assertSame($expectedmainquery, $mainquery);
// The has_query_order_by static method is protected. Use Reflection to call the method.
$method = new \ReflectionMethod('sqlsrv_native_moodle_database', 'has_query_order_by');
$result = $method->invoke(null, $sql);
$this->assertSame($expectedresult, $result);
}
}
/**
* Test class for testing temptables
*
* @copyright 2017 John Okely
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class temptables_tester {
/**
* Returns if one table, based in the information present in the store, is a temp table
*
* For easy testing, anything with the word 'temp' in it is considered temporary.
*
* @param string $tablename name without prefix of the table we are asking about
* @return bool true if the table is a temp table (based in the store info), false if not
*/
public function is_temptable($tablename) {
if (strpos($tablename, 'temp') === false) {
return false;
} else {
return true;
}
}
/**
* Dispose the temptables
*
* @return void
*/
public function dispose() {
}
}