ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ.. /** * 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'); $property->setAccessible(true); 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'); $rp->setAccessible(true); 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'); } }