diff --git a/.gitattributes b/.gitattributes index 0df73dc8d63..7947bdcfc1e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,5 @@ **/yui/build/** -diff **/amd/build/** -diff -lib/dml/oci_native_moodle_package.sql text eol=lf **.js.map -diff **-min.js -diff **.min.js -diff diff --git a/.upgradenotes/MDL-83172-2024112009513041.yml b/.upgradenotes/MDL-83172-2024112009513041.yml new file mode 100644 index 00000000000..724e86490d1 --- /dev/null +++ b/.upgradenotes/MDL-83172-2024112009513041.yml @@ -0,0 +1,5 @@ +issueNumber: MDL-83172 +notes: + core: + - message: Oracle support has been removed in LMS, with the exception of report builder which will be handled in a separate issue (MDL-80173). + type: removed diff --git a/admin/cli/install.php b/admin/cli/install.php index ee5547eb05a..1f664481fb8 100644 --- a/admin/cli/install.php +++ b/admin/cli/install.php @@ -217,7 +217,6 @@ $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'n 'auroramysql' => moodle_database::get_driver_instance('auroramysql', 'native'), 'mariadb'=> moodle_database::get_driver_instance('mariadb', 'native'), 'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'), - 'oci' => moodle_database::get_driver_instance('oci', 'native'), 'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver ); foreach ($databases as $type=>$database) { @@ -561,7 +560,6 @@ do { if ($interactive) { cli_separator(); cli_heading(get_string('dbprefix', 'install')); - //TODO: solve somehow the prefix trouble for oci. if ($options['prefix'] !== '') { $prompt = get_string('clitypevaluedefault', 'admin', $options['prefix']); } else { diff --git a/admin/environment.xml b/admin/environment.xml index 37916496422..834b0177082 100644 --- a/admin/environment.xml +++ b/admin/environment.xml @@ -4714,7 +4714,6 @@ - @@ -4892,8 +4891,6 @@ - - diff --git a/admin/mnet/testclient.php b/admin/mnet/testclient.php index f370866b0b8..63d67fbdc1f 100644 --- a/admin/mnet/testclient.php +++ b/admin/mnet/testclient.php @@ -91,7 +91,8 @@ if (!empty($hostid) && array_key_exists($hostid, $hosts)) { // this query is horrible and has to be remapped afterwards, because of the non-uniqueness // of the remoterep service (it has two plugins so far that use it) // it's possible to get a unique list back using a subquery with LIMIT but that would break oracle - // so it's best to just do this small query and then remap the results afterwards + // so it's best to just do this small query and then remap the results afterwards. + // TODO: Optimise the query, as Oracle-specific constraints no longer apply. $sql = ' SELECT DISTINCT ' . $DB->sql_concat('r.plugintype', "'_'", 'r.pluginname', "'_'", 's.name') . ' AS uniqueid, diff --git a/admin/tool/log/store/database/classes/helper.php b/admin/tool/log/store/database/classes/helper.php index bad2c1b0090..af7caab3447 100644 --- a/admin/tool/log/store/database/classes/helper.php +++ b/admin/tool/log/store/database/classes/helper.php @@ -43,7 +43,6 @@ class helper { 'native/mysqli' => \moodle_database::get_driver_instance('mysqli', 'native')->get_name(), 'native/mariadb' => \moodle_database::get_driver_instance('mariadb', 'native')->get_name(), 'native/pgsql' => \moodle_database::get_driver_instance('pgsql', 'native')->get_name(), - 'native/oci' => \moodle_database::get_driver_instance('oci', 'native')->get_name(), 'native/sqlsrv' => \moodle_database::get_driver_instance('sqlsrv', 'native')->get_name() ); } diff --git a/admin/tool/xmldb/actions/check_oracle_semantics/check_oracle_semantics.class.php b/admin/tool/xmldb/actions/check_oracle_semantics/check_oracle_semantics.class.php deleted file mode 100644 index f2128b11acc..00000000000 --- a/admin/tool/xmldb/actions/check_oracle_semantics/check_oracle_semantics.class.php +++ /dev/null @@ -1,153 +0,0 @@ -. - -/** - * @package tool_xmldb - * @copyright 2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * This class will check all the varchar2() columns - * in the Moodle installed DB, looking for incorrect (INT) - * length semanticas providing one SQL script to fix all - * them by changing to cross-db (CHAR) length semantics. - * See MDL-29322 for more details. - * - * @package tool_xmldb - * @copyright 2011 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class check_oracle_semantics extends XMLDBCheckAction { - - /** - * Init method, every subclass will have its own - */ - function init() { - $this->introstr = 'confirmcheckoraclesemantics'; - parent::init(); - - // Set own core attributes - - // Set own custom attributes - - // Get needed strings - $this->loadStrings(array( - 'wrongoraclesemantics' => 'tool_xmldb', - 'nowrongoraclesemanticsfound' => 'tool_xmldb', - 'yeswrongoraclesemanticsfound' => 'tool_xmldb', - 'expected' => 'tool_xmldb', - 'actual' => 'tool_xmldb', - )); - } - - protected function check_table(xmldb_table $xmldb_table, array $metacolumns) { - global $DB; - $o = ''; - $wrong_fields = array(); - - // Get and process XMLDB fields - if ($xmldb_fields = $xmldb_table->getFields()) { - $o .= '
    '; - foreach ($xmldb_fields as $xmldb_field) { - - // Get the type of the column, we only will process CHAR (VARCHAR2) ones - if ($xmldb_field->getType() != XMLDB_TYPE_CHAR) { - continue; - } - - $o.='
  • ' . $this->str['field'] . ': ' . $xmldb_field->getName() . ' '; - - // Get current semantic from dictionary, we only will process B (BYTE) ones - // suplying the SQL code to change them to C (CHAR) semantic - $params = array( - 'table_name' => core_text::strtoupper($DB->get_prefix() . $xmldb_table->getName()), - 'column_name' => core_text::strtoupper($xmldb_field->getName()), - 'data_type' => 'VARCHAR2'); - $currentsemantic = $DB->get_field_sql(' - SELECT char_used - FROM user_tab_columns - WHERE table_name = :table_name - AND column_name = :column_name - AND data_type = :data_type', $params); - - // If using byte semantics, we'll need to change them to char semantics - if ($currentsemantic == 'B') { - $info = '(' . $this->str['expected'] . " 'CHAR', " . $this->str['actual'] . " 'BYTE')"; - $o .= '' . $this->str['wrong'] . " $info"; - // Add the wrong field to the list - $obj = new stdClass(); - $obj->table = $xmldb_table; - $obj->field = $xmldb_field; - $wrong_fields[] = $obj; - } else { - $o .= '' . $this->str['ok'] . ''; - } - $o .= '
  • '; - } - $o .= '
'; - } - - return array($o, $wrong_fields); - } - - protected function display_results(array $wrong_fields) { - global $DB; - $dbman = $DB->get_manager(); - - $s = ''; - $r = ''; - $r.= ' '; - $r.= ' '; - $r.= ' '; - $r.= '
'; - $r.= '

' . $this->str['searchresults'] . '

'; - $r.= '

' . $this->str['wrongoraclesemantics'] . ': ' . count($wrong_fields) . '

'; - $r.= '
'; - - // If we have found wrong defaults inform about them - if (count($wrong_fields)) { - $r.= '

' . $this->str['yeswrongoraclesemanticsfound'] . '

'; - $r.= '
    '; - foreach ($wrong_fields as $obj) { - $xmldb_table = $obj->table; - $xmldb_field = $obj->field; - - $r.= '
  • ' . $this->str['table'] . ': ' . $xmldb_table->getName() . '. ' . - $this->str['field'] . ': ' . $xmldb_field->getName() . ', ' . - $this->str['expected'] . ' ' . "'CHAR'" . ' ' . - $this->str['actual'] . ' ' . "'BYTE'" . '
  • '; - - $sql = 'ALTER TABLE ' . $DB->get_prefix() . $xmldb_table->getName() . ' MODIFY ' . - $xmldb_field->getName() . ' VARCHAR2(' . $xmldb_field->getLength() . ' CHAR)'; - $sql = $dbman->generator->getEndedStatements($sql); - $s.= '' . str_replace("\n", '
    ', $sql) . '

    '; - } - $r.= '
'; - // Add the SQL statements (all together) - $r.= '
' . $s; - } else { - $r.= '

' . $this->str['nowrongoraclesemanticsfound'] . '

'; - } - $r.= '
'; - // Add the complete log message - $r.= '

' . $this->str['completelogbelow'] . '

'; - $r.= '
'; - - return $r; - } -} diff --git a/admin/tool/xmldb/actions/main_view/main_view.class.php b/admin/tool/xmldb/actions/main_view/main_view.class.php index 8071a71530b..f22ca08106e 100644 --- a/admin/tool/xmldb/actions/main_view/main_view.class.php +++ b/admin/tool/xmldb/actions/main_view/main_view.class.php @@ -56,7 +56,6 @@ class main_view extends XMLDBAction { 'checkdefaults' => 'tool_xmldb', 'checkforeignkeys' => 'tool_xmldb', 'checkbigints' => 'tool_xmldb', - 'checkoraclesemantics' => 'tool_xmldb', 'reconcilefiles' => 'tool_xmldb', 'doc' => 'tool_xmldb', 'filemodifiedoutfromeditor' => 'tool_xmldb', @@ -108,10 +107,6 @@ class main_view extends XMLDBAction { if ($DB->get_dbfamily() == 'mysql' || $DB->get_dbfamily() == 'postgres') { $b .= ' [' . $this->str['checkbigints'] . ']'; } - // The check semantics button (only for Oracle) MDL-29416 - if ($DB->get_dbfamily() == 'oracle') { - $b .= ' [' . $this->str['checkoraclesemantics'] . ']'; - } $b .= ' [' . $this->str['checkforeignkeys'] . ']'; $b .= '

'; // Send buttons to output diff --git a/admin/tool/xmldb/lang/en/tool_xmldb.php b/admin/tool/xmldb/lang/en/tool_xmldb.php index d2f4eb684ae..9d6d525eeda 100644 --- a/admin/tool/xmldb/lang/en/tool_xmldb.php +++ b/admin/tool/xmldb/lang/en/tool_xmldb.php @@ -59,13 +59,6 @@ Once generated you can copy such statements and execute them safely with your fa It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of missing indexes. -This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.'; -$string['confirmcheckoraclesemantics'] = 'This functionality will search for Oracle varchar2 columns using BYTE semantics in your Moodle server, generating (but not executing!) automatically the needed SQL statements to have all the columns converted to use CHAR semantics instead (better for cross-db compatibility and increased contents max. length). - -Once generated you can copy such statements and execute them safely with your favourite SQL interface (don\'t forget to backup your data before doing that). - -It\'s highly recommended to be running the latest (+ version) available of your Moodle release before executing the search of BYTE semantics. - This functionality doesn\'t perform any action against the DB (just reads from it), so can be safely executed at any moment.'; $string['confirmrevertchanges'] = 'Are you absolutely sure that you want to revert changes performed over:'; $string['create'] = 'Create'; @@ -127,8 +120,6 @@ $string['checkforeignkeys'] = 'Check foreign keys'; $string['check_foreign_keys'] = 'Look for foreign key violations'; $string['checkindexes'] = 'Check indexes'; $string['check_indexes'] = 'Look for missing DB indexes'; -$string['checkoraclesemantics'] = 'Check semantics'; -$string['check_oracle_semantics'] = 'Look for incorrect length semantics'; $string['duplicateindexname'] = 'Duplicate index name'; $string['incorrectfieldname'] = 'Incorrect name'; $string['index'] = 'Index'; @@ -164,7 +155,6 @@ $string['noreftablespecified'] = 'Specified reference table not found'; $string['noviolatedforeignkeysfound'] = 'No violated foreign keys found'; $string['nowrongdefaultsfound'] = 'No inconsistent default values have been found, your DB does not need further actions.'; $string['nowrongintsfound'] = 'No wrong integers have been found, your DB doesn\'t need further actions.'; -$string['nowrongoraclesemanticsfound'] = 'No Oracle columns using BYTE semantics have been found, your DB doesn\'t need further actions.'; $string['numberincorrectdecimals'] = 'Incorrect number of decimals for number field'; $string['numberincorrectlength'] = 'Incorrect length for number field'; $string['numberincorrectwholepart'] = 'Too big whole number part for number field'; @@ -222,7 +212,6 @@ $string['wrongints'] = 'Wrong integers found'; $string['wronglengthforenum'] = 'Incorrect length for enum field'; $string['wrongnumberofreffields'] = 'Wrong number of reference fields'; $string['wrongreservedwords'] = 'Currently used reserved words
(note that table names aren\'t important if using $CFG->prefix)'; -$string['wrongoraclesemantics'] = 'Wrong Oracle BYTE semantics found'; $string['yesextraindexesfound'] = 'The following additional indexes were found.'; $string['yesmissingindexesfound'] = '

Some missing indexes have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all of them. Remember to backup your data first!

After doing that, it\'s highly recommended to execute this utility again to check that no more missing indexes are found.

'; @@ -230,6 +219,4 @@ $string['yeswrongdefaultsfound'] = '

Some inconsistent defaults have been foun

After doing that, it\'s highly recommended to execute this utility again to check that no more inconsistent defaults are found.

'; $string['yeswrongintsfound'] = '

Some wrong integers have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to fix them. Remember to backup your data first!

After fixing them, it is highly recommended to execute this utility again to check that no more wrong integers are found.

'; -$string['yeswrongoraclesemanticsfound'] = '

Some Oracle columns using BYTE semantics have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to convert them all. Remember to backup your data first!

-

After doing that, it\'s highly recommended to execute this utility again to check that no more wrong semantics are found.

'; $string['privacy:metadata'] = 'The XMLDB editor plugin does not store any personal data.'; diff --git a/analytics/classes/analysis.php b/analytics/classes/analysis.php index f11bfae03d7..43294a39348 100644 --- a/analytics/classes/analysis.php +++ b/analytics/classes/analysis.php @@ -532,7 +532,7 @@ class analysis { // Insert from the beginning. $remaining = array_splice($newcalculations, $batchsize); - // Sorry mssql and oracle, this will be slow. + // Sorry mssql, this will be slow. $DB->insert_records('analytics_indicator_calc', $newcalculations); $newcalculations = $remaining; } diff --git a/analytics/tests/model_test.php b/analytics/tests/model_test.php index 5f91d183fbf..6c6ed23ebf4 100644 --- a/analytics/tests/model_test.php +++ b/analytics/tests/model_test.php @@ -525,7 +525,7 @@ final class model_test extends \advanced_testcase { $this->markTestSkipped('PHPUNIT_LONGTEST is not defined'); } - // 10000 should be enough to make oracle and mssql fail, if we want pgsql to fail we need around 70000 + // 10000 should be enough to make mssql fail, if we want pgsql to fail we need around 70000 // users, that is a few minutes just to create the users. $nusers = 10000; diff --git a/auth/db/lang/en/auth_db.php b/auth/db/lang/en/auth_db.php index 44b4aea073b..f8022a2bb44 100644 --- a/auth/db/lang/en/auth_db.php +++ b/auth/db/lang/en/auth_db.php @@ -55,7 +55,7 @@ $string['auth_dbsetupsqlhelp'] = 'SQL command for special database setup, often $string['auth_dbsuspenduser'] = 'Suspended user {$a->name} id {$a->id}'; $string['auth_dbsuspendusererror'] = 'Error suspending user {$a}'; $string['auth_dbsybasequoting'] = 'Use sybase quotes'; -$string['auth_dbsybasequotinghelp'] = 'Sybase style single quote escaping - needed for Oracle, MS SQL and some other databases. Do not use for MySQL!'; +$string['auth_dbsybasequotinghelp'] = 'Sybase style single quote escaping - needed for MS SQL and some other databases. Do not use for MySQL!'; $string['auth_dbsyncuserstask'] = 'Synchronise users task'; $string['auth_dbtable'] = 'Name of the table in the database'; $string['auth_dbtable_key'] = 'Table'; diff --git a/auth/db/tests/db_test.php b/auth/db/tests/db_test.php index 9abed727bb5..8c7de967a5e 100644 --- a/auth/db/tests/db_test.php +++ b/auth/db/tests/db_test.php @@ -80,11 +80,6 @@ final class db_test extends \advanced_testcase { } break; - case 'oracle': - set_config('type', 'oci8po', 'auth_db'); - set_config('sybasequoting', '1', 'auth_db'); - break; - case 'postgres': set_config('type', 'postgres7', 'auth_db'); $setupsql = "SET NAMES 'UTF-8'"; diff --git a/composer.json b/composer.json index 4c85f8a25c0..45fde0b9648 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,6 @@ "ext-mysqli": "Needed when Moodle uses MySQL or MariaDB database.", "ext-pgsql": "Needed when Moodle uses PostgreSQL database.", "ext-sqlsrv": "Needed when Moodle uses MS SQL Server database.", - "ext-oci8": "Needed when Moodle uses Oracle database.", "ext-tokenizer": "Enabling Tokenizer PHP extension is recommended, it improves Moodle Networking functionality.", "ext-soap": "Enabling SOAP PHP extension is useful for web services and some plugins.", "ext-exif": "Enabling Exif PHP extension is recommended, it is used by Moodle to parse image meta data." diff --git a/config-dist.php b/config-dist.php index 3cd38fc0956..ab1d7adcc30 100644 --- a/config-dist.php +++ b/config-dist.php @@ -38,7 +38,7 @@ $CFG = new stdClass(); // will be stored. This database must already have been created // // and a username/password created to access it. // -$CFG->dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'auroramysql', 'sqlsrv' or 'oci' +$CFG->dbtype = 'pgsql'; // 'pgsql', 'mariadb', 'mysqli', 'auroramysql', or 'sqlsrv' $CFG->dblibrary = 'native'; // 'native' only at the moment $CFG->dbhost = 'localhost'; // eg 'localhost' or 'db.isp.com' or IP $CFG->dbname = 'moodle'; // database name, eg moodle @@ -605,7 +605,7 @@ $CFG->admin = 'admin'; // // Moodle 2.7 introduces a locking api for critical tasks (e.g. cron). // The default locking system to use is DB locking for Postgres, MySQL, MariaDB and -// file locking for Oracle and SQLServer. If $CFG->preventfilelocking is set, then the +// file locking for SQLServer. If $CFG->preventfilelocking is set, then the // default will always be DB locking. It can be manually set to one of the lock // factory classes listed below, or one of your own custom classes implementing the // \core\lock\lock_factory interface. diff --git a/enrol/database/lang/en/enrol_database.php b/enrol/database/lang/en/enrol_database.php index a9cd25908ee..96ac956d36a 100644 --- a/enrol/database/lang/en/enrol_database.php +++ b/enrol/database/lang/en/enrol_database.php @@ -33,7 +33,7 @@ $string['dbpass'] = 'Database password'; $string['dbsetupsql'] = 'Database setup command'; $string['dbsetupsql_desc'] = 'SQL command for special database setup, often used to setup communication encoding - example for MySQL and PostgreSQL: SET NAMES \'utf8\''; $string['dbsybasequoting'] = 'Use sybase quotes'; -$string['dbsybasequoting_desc'] = 'Sybase style single quote escaping - needed for Oracle, MS SQL and some other databases. Do not use for MySQL!'; +$string['dbsybasequoting_desc'] = 'Sybase style single quote escaping - needed for MS SQL and some other databases. Do not use for MySQL!'; $string['dbtype'] = 'Database driver'; $string['dbtype_desc'] = 'ADOdb database driver name, type of the external database engine.'; $string['dbuser'] = 'Database user'; diff --git a/enrol/database/tests/sync_test.php b/enrol/database/tests/sync_test.php index 7ac41d3a8c6..ad1b56f9fcc 100644 --- a/enrol/database/tests/sync_test.php +++ b/enrol/database/tests/sync_test.php @@ -82,11 +82,6 @@ final class sync_test extends \advanced_testcase { } break; - case 'oracle': - set_config('dbtype', 'oci8po', 'enrol_database'); - set_config('dbsybasequoting', '1', 'enrol_database'); - break; - case 'postgres': set_config('dbtype', 'postgres7', 'enrol_database'); $setupsql = "SET NAMES 'UTF-8'"; diff --git a/install.php b/install.php index 7fc10dbddc4..c0137efbcda 100644 --- a/install.php +++ b/install.php @@ -495,7 +495,6 @@ if ($config->stage == INSTALL_DATABASETYPE) { 'auroramysql' => moodle_database::get_driver_instance('auroramysql', 'native'), 'mariadb'=> moodle_database::get_driver_instance('mariadb', 'native'), 'pgsql' => moodle_database::get_driver_instance('pgsql', 'native'), - 'oci' => moodle_database::get_driver_instance('oci', 'native'), 'sqlsrv' => moodle_database::get_driver_instance('sqlsrv', 'native'), // MS SQL*Server PHP driver ); diff --git a/lang/en/admin.php b/lang/en/admin.php index 2b2a88cbadd..b461862c01b 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -986,7 +986,6 @@ $string['opensslrecommended'] = 'Installing the optional OpenSSL library is high $string['opensslrequired'] = 'The OpenSSL PHP extension is now required by Moodle to provide stronger cryptographic services.'; $string['opentowebcrawlers'] = 'Open to search engines'; $string['optionalmaintenancemessage'] = 'Optional maintenance message'; -$string['oracledatabaseinuse'] = 'Oracle DB support is to be removed. Moodle 4.5 will be the last version with Oracle DB support. For details, see the announcement on moodle.org Oracle database support in LMS deprecation.'; $string['order1'] = 'First'; $string['order2'] = 'Second'; $string['order3'] = 'Third'; diff --git a/lang/en/install.php b/lang/en/install.php index a54a1aa8800..da0bf4da0f5 100644 --- a/lang/en/install.php +++ b/lang/en/install.php @@ -187,9 +187,6 @@ $string['nativemysqlihelp'] = '

The database is where most of the Moodle setti

The database name, username, and password are required fields; table prefix is optional.

The database name may contain only alphanumeric characters, dollar ($) and underscore (_).

If the database currently does not exist, and the user you specify has permission, Moodle will attempt to create a new database with the correct permissions and settings.

'; -$string['nativeoci'] = 'Oracle (native/oci)'; -$string['nativeocihelp'] = 'Now you need to configure the database where most Moodle data will be stored. -This database must already have been created and a username and password created to access it. Table prefix is mandatory.'; $string['nativepgsql'] = 'PostgreSQL (native/pgsql)'; $string['nativepgsqlhelp'] = '

The database is where most of the Moodle settings and data are stored and must be configured here.

The database name, username, password and table prefix are required fields.

@@ -198,7 +195,6 @@ $string['nativesqlsrv'] = 'SQL*Server Microsoft (native/sqlsrv)'; $string['nativesqlsrvhelp'] = 'Now you need to configure the database where most Moodle data will be stored. This database must already have been created and a username and password created to access it. Table prefix is mandatory.'; $string['nativesqlsrvnodriver'] = 'Microsoft Drivers for SQL Server for PHP are not installed or not configured properly.'; -$string['ociextensionisnotpresentinphp'] = 'PHP has not been properly configured with the OCI8 extension so that it can communicate with Oracle. Please check your php.ini file or recompile PHP.'; $string['pass'] = 'Pass'; $string['paths'] = 'Paths'; $string['pathserrcreatedataroot'] = 'Data directory ({$a->dataroot}) cannot be created by the installer.'; diff --git a/lib/behat/lib.php b/lib/behat/lib.php index bbe95e66099..015580d1129 100644 --- a/lib/behat/lib.php +++ b/lib/behat/lib.php @@ -353,7 +353,7 @@ function behat_is_test_site() { * Fix variables for parallel behat testing. * - behat_wwwroot = behat_wwwroot{behatrunprocess} * - behat_dataroot = behat_dataroot{behatrunprocess} - * - behat_prefix = behat_prefix.{behatrunprocess}_ (For oracle it will be firstletter of prefix and behatrunprocess) + * - behat_prefix = behat_prefix.{behatrunprocess} **/ function behat_update_vars_for_process() { global $CFG; @@ -384,14 +384,8 @@ function behat_update_vars_for_process() { } // Set behat_prefix for db, just suffix run process number, to avoid max length exceed. - // For oracle only 2 letter prefix is possible. // NOTE: This will not work for parallel process > 9. - if ($CFG->dbtype === 'oci') { - $CFG->behat_prefix = substr($CFG->behat_prefix, 0, 1); - $CFG->behat_prefix .= "{$behatrunprocess}"; - } else { - $CFG->behat_prefix .= "{$behatrunprocess}_"; - } + $CFG->behat_prefix .= "{$behatrunprocess}_"; if (!empty($CFG->behat_parallel_run[$behatrunprocess - 1])) { // Override allowed config vars. diff --git a/lib/classes/context.php b/lib/classes/context.php index 34f42fe8799..bfa8455dc5b 100644 --- a/lib/classes/context.php +++ b/lib/classes/context.php @@ -423,15 +423,6 @@ abstract class context extends stdClass implements IteratorAggregate { ct.depth = temp.depth, ct.locked = temp.locked WHERE ct.id = temp.id"; - } else if ($dbfamily == 'oracle') { - $updatesql = "UPDATE {context} ct - SET (ct.path, ct.depth, ct.locked) = - (SELECT temp.path, temp.depth, temp.locked - FROM {context_temp} temp - WHERE temp.id=ct.id) - WHERE EXISTS (SELECT 'x' - FROM {context_temp} temp - WHERE temp.id = ct.id)"; } else if ($dbfamily == 'postgres' || $dbfamily == 'mssql') { $updatesql = "UPDATE {context} SET path = temp.path, diff --git a/lib/classes/output/user_picture.php b/lib/classes/output/user_picture.php index ed6266d8a2b..db394dc6e5f 100644 --- a/lib/classes/output/user_picture.php +++ b/lib/classes/output/user_picture.php @@ -136,7 +136,7 @@ class user_picture implements renderable { * * @param string $tableprefix name of database table prefix in query * @param null|array $extrafields extra fields to be included in result - * Do not include TEXT columns because it would break SELECT DISTINCT in MSSQL and ORACLE. + * Do not include TEXT columns because it would break SELECT DISTINCT in MSSQL. * @param string $idalias alias of id field * @param string $fieldprefix prefix to add to all columns in their aliases, does not apply to 'id' * @return string diff --git a/lib/datalib.php b/lib/datalib.php index 4bfcd6069f9..5cc275c2b3d 100644 --- a/lib/datalib.php +++ b/lib/datalib.php @@ -733,21 +733,17 @@ function get_courses_search($searchterms, $sort, $page, $recordsperpage, &$total $i = 0; - // Thanks Oracle for your non-ansi concat and type limits in coalesce. MDL-29912 - if ($DB->get_dbfamily() == 'oracle') { - $concat = "(c.summary|| ' ' || c.fullname || ' ' || c.idnumber || ' ' || c.shortname)"; - } else { - $concat = $DB->sql_concat("COALESCE(c.summary, '')", "' '", 'c.fullname', "' '", 'c.idnumber', "' '", 'c.shortname'); - } + $concat = $DB->sql_concat("COALESCE(c.summary, '')", "' '", 'c.fullname', "' '", 'c.idnumber', "' '", 'c.shortname'); foreach ($searchterms as $searchterm) { $i++; - $NOT = false; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle - /// will use it to simulate the "-" operator with LIKE clause + // Initially we aren't going to perform NOT LIKE searches, only MSSQL + // will use it to simulate the "-" operator with LIKE clause. + $NOT = false; - /// Under Oracle and MSSQL, trim the + and - operators and perform - /// simpler LIKE (or NOT LIKE) queries + // Under MSSQL, trim the + and - operators and perform + // simpler LIKE (or NOT LIKE) queries. if (!$DB->sql_regex_supported()) { if (substr($searchterm, 0, 1) == '-') { $NOT = true; @@ -1107,7 +1103,7 @@ function get_my_remotecourses($userid=0) { $userid = $USER->id; } - // we can not use SELECT DISTINCT + text field (summary) because of MS SQL and Oracle, subselect used therefore + // We can not use SELECT DISTINCT + text field (summary) because of MS SQL, subselect used therefore. $sql = "SELECT c.id, c.remoteid, c.shortname, c.fullname, c.hostid, c.summary, c.summaryformat, c.categoryname AS cat_name, h.name AS hostname diff --git a/lib/db/upgradelib.php b/lib/db/upgradelib.php index 876b05dfb2e..286adddf229 100644 --- a/lib/db/upgradelib.php +++ b/lib/db/upgradelib.php @@ -1423,19 +1423,6 @@ function upgrade_block_set_my_user_parent_context( SET bi.parentcontextid = bic.contextid WHERE bi.id = bic.instanceid EOF; - } else if ($dbfamily === 'oracle') { - $sql = <<add_field($table, $field); // Copy the 'true' values from the old field to the new field. - if ($DB->get_dbfamily() === 'oracle') { - // It's tricky to use the binary column in the WHERE clause in Oracle DBs. - // Let's go updating the records one by one. It's nasty, but it's only done for instances with Oracle DBs. - // The normal SQL UPDATE statement will be used for other DBs. - $columns = implode(', ', ['id', $tmpfieldname, $fieldname]); - $records = $DB->get_recordset($tablename, null, '', $columns); - if ($records->valid()) { - foreach ($records as $record) { - if (!$record->$tmpfieldname) { - continue; - } - $DB->set_field($tablename, $fieldname, 1, ['id' => $record->id]); - } - } - $records->close(); - } else { - $sql = 'UPDATE {' . $tablename . '} - SET ' . $fieldname . ' = 1 - WHERE ' . $tmpfieldname . ' = ?'; - $DB->execute($sql, [1]); - } + $sql = 'UPDATE {' . $tablename . '} + SET ' . $fieldname . ' = 1 + WHERE ' . $tmpfieldname . ' = ?'; + $DB->execute($sql, [1]); // Drop the old field. $oldfield = new xmldb_field($tmpfieldname); diff --git a/lib/ddl/oracle_sql_generator.php b/lib/ddl/oracle_sql_generator.php deleted file mode 100644 index ea7d8067414..00000000000 --- a/lib/ddl/oracle_sql_generator.php +++ /dev/null @@ -1,756 +0,0 @@ -. - -/** - * Oracle specific SQL code generator. - * - * @package core_ddl - * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com - * 2001-3001 Eloy Lafuente (stronk7) http://contiento.com - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->libdir.'/ddl/sql_generator.php'); - -/** - * This class generate SQL code to be used against Oracle - * It extends XMLDBgenerator so everything can be - * overridden as needed to generate correct SQL. - * - * @package core_ddl - * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com - * 2001-3001 Eloy Lafuente (stronk7) http://contiento.com - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class oracle_sql_generator extends sql_generator { - - // Only set values that are different from the defaults present in XMLDBgenerator - - /** - * @var string To be automatically added at the end of each statement. - * note: Using "/" because the standard ";" isn't good for stored procedures (triggers) - */ - public $statement_end = "\n/"; - - /** @var string Proper type for NUMBER(x) in this DB. */ - public $number_type = 'NUMBER'; - - /** - * @var string To define the default to set for NOT NULLs CHARs without default (null=do nothing). - * note: Using this whitespace here because Oracle doesn't distinguish empty and null! :-( - */ - public $default_for_char = ' '; - - /** @var bool To specify if the generator must use some DEFAULT clause to drop defaults.*/ - public $drop_default_value_required = true; - - /** @var string The DEFAULT clause required to drop defaults.*/ - public $drop_default_value = null; - - /** @var bool To decide if the default clause of each field must go after the null clause.*/ - public $default_after_null = false; - - /** @var bool True if the generator needs to add extra code to generate the sequence fields.*/ - public $sequence_extra_code = true; - - /** @var string The particular name for inline sequences in this generator.*/ - public $sequence_name = ''; - - /** @var string The SQL template to alter columns where the 'TABLENAME' and 'COLUMNSPECS' keywords are dynamically replaced.*/ - public $alter_column_sql = 'ALTER TABLE TABLENAME MODIFY (COLUMNSPECS)'; - - /** @var int var ugly Oracle hack - size of the sequences values cache (20 = Default)*/ - public $sequence_cache_size = 20; - - /** - * 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) { - - if (is_string($table)) { - $tablename = $table; - $xmldb_table = new xmldb_table($tablename); - } else { - $tablename = $table->getName(); - $xmldb_table = $table; - } - // From http://www.acs.ilstu.edu/docs/oracle/server.101/b10759/statements_2011.htm - $value = (int)$this->mdb->get_field_sql('SELECT MAX(id) FROM {'.$tablename.'}'); - $value++; - - $seqname = $this->getSequenceFromDB($xmldb_table); - - if (!$seqname) { - // Fallback, seqname not found, something is wrong. Inform and use the alternative getNameForObject() method - $seqname = $this->getNameForObject($table, 'id', 'seq'); - } - - return array ("DROP SEQUENCE $seqname", - "CREATE SEQUENCE $seqname START WITH $value INCREMENT BY 1 NOMAXVALUE CACHE $this->sequence_cache_size"); - } - - /** - * Given one xmldb_table, returns it's correct name, depending of all the parametrization - * Overridden to allow change of names in temp tables - * - * @param xmldb_table table whose name we want - * @param boolean to specify if the name must be quoted (if reserved word, only!) - * @return string the correct name of the table - */ - public function getTableName(xmldb_table $xmldb_table, $quoted=true) { - // Get the name, supporting special oci names for temp tables - if ($this->temptables->is_temptable($xmldb_table->getName())) { - $tablename = $this->temptables->get_correct_name($xmldb_table->getName()); - } else { - $tablename = $this->prefix . $xmldb_table->getName(); - } - - // Apply quotes optionally - if ($quoted) { - $tablename = $this->getEncQuoted($tablename); - } - - return $tablename; - } - - public function getCreateIndexSQL($xmldb_table, $xmldb_index) { - if ($error = $xmldb_index->validateDefinition($xmldb_table)) { - throw new coding_exception($error); - } - - $indexfields = $this->getEncQuoted($xmldb_index->getFields()); - - $unique = ''; - $suffix = 'ix'; - if ($xmldb_index->getUnique()) { - $unique = ' UNIQUE'; - $suffix = 'uix'; - - $nullablefields = $this->get_nullable_fields_in_index($xmldb_table, $xmldb_index); - if ($nullablefields) { - // If this is a unique index with nullable fields, then we have to - // apply the work-around from https://community.oracle.com/message/9518046#9518046. - // - // For example if you have a unique index on the three columns - // (required, option1, option2) where the first one is non-null, - // and the others nullable, then the SQL will end up as - // - // CREATE UNIQUE INDEX index_name ON table_name ( - // CASE WHEN option1 IS NOT NULL AND option2 IS NOT NULL THEN required ELSE NULL END, - // CASE WHEN option1 IS NOT NULL AND option2 IS NOT NULL THEN option1 ELSE NULL END, - // CASE WHEN option1 IS NOT NULL AND option2 IS NOT NULL THEN option2 ELSE NULL END) - // - // Basically Oracle behaves according to the standard if either - // none of the columns are NULL or all columns contain NULL. Therefore, - // if any column is NULL, we treat them all as NULL for the index. - $conditions = []; - foreach ($nullablefields as $fieldname) { - $conditions[] = $this->getEncQuoted($fieldname) . - ' IS NOT NULL'; - } - $condition = implode(' AND ', $conditions); - - $updatedindexfields = []; - foreach ($indexfields as $fieldname) { - $updatedindexfields[] = 'CASE WHEN ' . $condition . ' THEN ' . - $fieldname . ' ELSE NULL END'; - } - $indexfields = $updatedindexfields; - } - - } - - $index = 'CREATE' . $unique . ' INDEX '; - $index .= $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_index->getFields()), $suffix); - $index .= ' ON ' . $this->getTableName($xmldb_table); - $index .= ' (' . implode(', ', $indexfields) . ')'; - - return array($index); - } - - /** - * Given one correct xmldb_table, returns the SQL statements - * to create temporary table (inside one array). - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @return array of sql statements - */ - public function getCreateTempTableSQL($xmldb_table) { - $this->temptables->add_temptable($xmldb_table->getName()); - $sqlarr = $this->getCreateTableSQL($xmldb_table); - $sqlarr = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE GLOBAL TEMPORARY TABLE $1 ON COMMIT PRESERVE ROWS', $sqlarr); - return $sqlarr; - } - - /** - * Given one correct xmldb_table, returns the SQL statements - * to drop it (inside one array). - * - * @param xmldb_table $xmldb_table The table to drop. - * @return array SQL statement(s) for dropping the specified table. - */ - public function getDropTableSQL($xmldb_table) { - $sqlarr = parent::getDropTableSQL($xmldb_table); - if ($this->temptables->is_temptable($xmldb_table->getName())) { - array_unshift($sqlarr, "TRUNCATE TABLE ". $this->getTableName($xmldb_table)); // oracle requires truncate before being able to drop a temp table - } - return $sqlarr; - } - - /** - * Given one XMLDB Type, length and decimals, returns the DB proper SQL type. - * - * @param int $xmldb_type The xmldb_type defined constant. XMLDB_TYPE_INTEGER and other XMLDB_TYPE_* constants. - * @param int $xmldb_length The length of that data type. - * @param int $xmldb_decimals The decimal places of precision of the data type. - * @return string The DB defined data type. - */ - public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) { - - switch ($xmldb_type) { - case XMLDB_TYPE_INTEGER: // See http://www.acs.ilstu.edu/docs/oracle/server.101/b10759/sql_elements001.htm#sthref86. - if (empty($xmldb_length)) { - $xmldb_length = 10; - } - $dbtype = 'NUMBER(' . $xmldb_length . ')'; - break; - case XMLDB_TYPE_FLOAT: - case XMLDB_TYPE_NUMBER: - $dbtype = $this->number_type; - if (!empty($xmldb_length)) { - $dbtype .= '(' . $xmldb_length; - if (!empty($xmldb_decimals)) { - $dbtype .= ',' . $xmldb_decimals; - } - $dbtype .= ')'; - } - break; - case XMLDB_TYPE_CHAR: - // Do not use NVARCHAR2 here because it has hardcoded 1333 char limit, - // VARCHAR2 allows us to create larger fields that error out later during runtime - // only when too many non-ascii utf-8 chars present. - $dbtype = 'VARCHAR2'; - if (empty($xmldb_length)) { - $xmldb_length='255'; - } - $dbtype .= '(' . $xmldb_length . ' CHAR)'; // CHAR is required because BYTE is the default - break; - case XMLDB_TYPE_TEXT: - $dbtype = 'CLOB'; - break; - case XMLDB_TYPE_BINARY: - $dbtype = 'BLOB'; - break; - case XMLDB_TYPE_DATETIME: - $dbtype = 'DATE'; - break; - } - return $dbtype; - } - - /** - * Returns the code (array of statements) needed - * to create one sequence for the xmldb_table and xmldb_field passed in. - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @param xmldb_field $xmldb_field The xmldb_field object instance. - * @return array Array of SQL statements to create the sequence. - */ - public function getCreateSequenceSQL($xmldb_table, $xmldb_field) { - - $results = array(); - - $sequence_name = $this->getNameForObject($xmldb_table->getName(), $xmldb_field->getName(), 'seq'); - - $sequence = "CREATE SEQUENCE $sequence_name START WITH 1 INCREMENT BY 1 NOMAXVALUE CACHE $this->sequence_cache_size"; - - $results[] = $sequence; - - $results = array_merge($results, $this->getCreateTriggerSQL ($xmldb_table, $xmldb_field, $sequence_name)); - - return $results; - } - - /** - * Returns the code needed to create one trigger for the xmldb_table and xmldb_field passed - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @param xmldb_field $xmldb_field The xmldb_field object instance. - * @param string $sequence_name - * @return array Array of SQL statements to create the sequence. - */ - public function getCreateTriggerSQL($xmldb_table, $xmldb_field, $sequence_name) { - - $trigger_name = $this->getNameForObject($xmldb_table->getName(), $xmldb_field->getName(), 'trg'); - - $trigger = "CREATE TRIGGER " . $trigger_name; - $trigger.= "\n BEFORE INSERT"; - $trigger.= "\nON " . $this->getTableName($xmldb_table); - $trigger.= "\n FOR EACH ROW"; - $trigger.= "\nBEGIN"; - $trigger.= "\n IF :new." . $this->getEncQuoted($xmldb_field->getName()) . ' IS NULL THEN'; - $trigger.= "\n SELECT " . $sequence_name . '.nextval INTO :new.' . $this->getEncQuoted($xmldb_field->getName()) . " FROM dual;"; - $trigger.= "\n END IF;"; - $trigger.= "\nEND;"; - - return array($trigger); - } - - /** - * Returns the code needed to drop one sequence for the xmldb_table and xmldb_field passed - * Can, optionally, specify if the underlying trigger will be also dropped - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @param xmldb_field $xmldb_field The xmldb_field object instance. - * @param bool $include_trigger - * @return array Array of SQL statements to create the sequence. - */ - public function getDropSequenceSQL($xmldb_table, $xmldb_field, $include_trigger=false) { - - $result = array(); - - if ($sequence_name = $this->getSequenceFromDB($xmldb_table)) { - $result[] = "DROP SEQUENCE " . $sequence_name; - } - - if ($trigger_name = $this->getTriggerFromDB($xmldb_table) && $include_trigger) { - $result[] = "DROP TRIGGER " . $trigger_name; - } - - return $result; - } - - /** - * Returns the code (array of statements) needed to add one comment to the table. - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @return array Array of SQL statements to add one comment to the table. - */ - function getCommentSQL($xmldb_table) { - - $comment = "COMMENT ON TABLE " . $this->getTableName($xmldb_table); - $comment.= " IS '" . $this->addslashes(substr($xmldb_table->getComment(), 0, 250)) . "'"; - - return array($comment); - } - - /** - * Returns the code (array of statements) needed to execute extra statements on table drop - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @return array Array of extra SQL statements to drop a table. - */ - public function getDropTableExtraSQL($xmldb_table) { - $xmldb_field = new xmldb_field('id'); // Fields having sequences should be exclusively, id. - return $this->getDropSequenceSQL($xmldb_table, $xmldb_field, false); - } - - /** - * Returns the code (array of statements) needed to execute extra statements on table rename. - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @param string $newname The new name for the table. - * @return array Array of extra SQL statements to rename a table. - */ - public function getRenameTableExtraSQL($xmldb_table, $newname) { - - $results = array(); - - $xmldb_field = new xmldb_field('id'); // Fields having sequences should be exclusively, id. - - $oldseqname = $this->getSequenceFromDB($xmldb_table); - $newseqname = $this->getNameForObject($newname, $xmldb_field->getName(), 'seq'); - - $oldtriggername = $this->getTriggerFromDB($xmldb_table); - $newtriggername = $this->getNameForObject($newname, $xmldb_field->getName(), 'trg'); - - // Drop old trigger (first of all) - $results[] = "DROP TRIGGER " . $oldtriggername; - - // Rename the sequence, disablig CACHE before and enablig it later - // to avoid consuming of values on rename - $results[] = 'ALTER SEQUENCE ' . $oldseqname . ' NOCACHE'; - $results[] = 'RENAME ' . $oldseqname . ' TO ' . $newseqname; - $results[] = 'ALTER SEQUENCE ' . $newseqname . ' CACHE ' . $this->sequence_cache_size; - - // Create new trigger - $newt = new xmldb_table($newname); // Temp table for trigger code generation - $results = array_merge($results, $this->getCreateTriggerSQL($newt, $xmldb_field, $newseqname)); - - return $results; - } - - /** - * Given one xmldb_table and one xmldb_field, return the SQL statements needed to alter the field in the table. - * - * Oracle has some severe limits: - * - clob and blob fields doesn't allow type to be specified - * - error is dropped if the null/not null clause is specified and hasn't changed - * - changes in precision/decimals of numeric fields drop an ORA-1440 error - * - * @param xmldb_table $xmldb_table The table related to $xmldb_field. - * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from. - * @param string $skip_type_clause The type clause on alter columns, NULL by default. - * @param string $skip_default_clause The default clause on alter columns, NULL by default. - * @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default. - * @return string The field altering SQL statement. - */ - public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) { - - $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause; - $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause; - $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause; - - $results = array(); // To store all the needed SQL commands - - // Get the quoted name of the table and field - $tablename = $this->getTableName($xmldb_table); - $fieldname = $xmldb_field->getName(); - - // Take a look to field metadata - $meta = $this->mdb->get_columns($xmldb_table->getName()); - $metac = $meta[$fieldname]; - $oldmetatype = $metac->meta_type; - - $oldlength = $metac->max_length; - // To calculate the oldlength if the field is numeric, we need to perform one extra query - // because ADOdb has one bug here. http://phplens.com/lens/lensforum/msgs.php?id=15883 - if ($oldmetatype == 'N') { - $uppertablename = strtoupper($tablename); - $upperfieldname = strtoupper($fieldname); - if ($col = $this->mdb->get_record_sql("SELECT cname, precision - FROM col - WHERE tname = ? AND cname = ?", - array($uppertablename, $upperfieldname))) { - $oldlength = $col->precision; - } - } - $olddecimals = empty($metac->scale) ? null : $metac->scale; - $oldnotnull = empty($metac->not_null) ? false : $metac->not_null; - $olddefault = empty($metac->default_value) || strtoupper($metac->default_value) == 'NULL' ? null : $metac->default_value; - - $typechanged = true; //By default, assume that the column type has changed - $precisionchanged = true; //By default, assume that the column precision has changed - $decimalchanged = true; //By default, assume that the column decimal has changed - $defaultchanged = true; //By default, assume that the column default has changed - $notnullchanged = true; //By default, assume that the column notnull has changed - - $from_temp_fields = false; //By default don't assume we are going to use temporal fields - - // Detect if we are changing the type of the column - if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER && $oldmetatype == 'I') || - ($xmldb_field->getType() == XMLDB_TYPE_NUMBER && $oldmetatype == 'N') || - ($xmldb_field->getType() == XMLDB_TYPE_FLOAT && $oldmetatype == 'F') || - ($xmldb_field->getType() == XMLDB_TYPE_CHAR && $oldmetatype == 'C') || - ($xmldb_field->getType() == XMLDB_TYPE_TEXT && $oldmetatype == 'X') || - ($xmldb_field->getType() == XMLDB_TYPE_BINARY && $oldmetatype == 'B')) { - $typechanged = false; - } - // Detect if precision has changed - if (($xmldb_field->getType() == XMLDB_TYPE_TEXT) || - ($xmldb_field->getType() == XMLDB_TYPE_BINARY) || - ($oldlength == -1) || - ($xmldb_field->getLength() == $oldlength)) { - $precisionchanged = false; - } - // Detect if decimal has changed - if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER) || - ($xmldb_field->getType() == XMLDB_TYPE_CHAR) || - ($xmldb_field->getType() == XMLDB_TYPE_TEXT) || - ($xmldb_field->getType() == XMLDB_TYPE_BINARY) || - (!$xmldb_field->getDecimals()) || - (!$olddecimals) || - ($xmldb_field->getDecimals() == $olddecimals)) { - $decimalchanged = false; - } - // Detect if we are changing the default - if (($xmldb_field->getDefault() === null && $olddefault === null) || - ($xmldb_field->getDefault() === $olddefault) || //Check both equality and - ("'" . $xmldb_field->getDefault() . "'" === $olddefault)) { //Equality with quotes because ADOdb returns the default with quotes - $defaultchanged = false; - } - - // Detect if we are changing the nullability - if (($xmldb_field->getNotnull() === $oldnotnull)) { - $notnullchanged = false; - } - - // If type has changed or precision or decimal has changed and we are in one numeric field - // - create one temp column with the new specs - // - fill the new column with the values from the old one - // - drop the old column - // - rename the temp column to the original name - if (($typechanged) || (($oldmetatype == 'N' || $oldmetatype == 'I') && ($precisionchanged || $decimalchanged))) { - $tempcolname = $xmldb_field->getName() . '___tmp'; // Short tmp name, surely not conflicting ever - if (strlen($tempcolname) > 30) { // Safeguard we don't excess the 30cc limit - $tempcolname = 'ongoing_alter_column_tmp'; - } - // Prevent temp field to have both NULL/NOT NULL and DEFAULT constraints - $skip_notnull_clause = true; - $skip_default_clause = true; - $xmldb_field->setName($tempcolname); - // Drop the temp column, in case it exists (due to one previous failure in conversion) - // really ugly but we cannot enclose DDL into transaction :-( - if (isset($meta[$tempcolname])) { - $results = array_merge($results, $this->getDropFieldSQL($xmldb_table, $xmldb_field)); - } - // Create the temporal column - $results = array_merge($results, $this->getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause, $skip_type_clause, $skip_notnull_clause)); - // Copy contents from original col to the temporal one - - // From TEXT to integer/number we need explicit conversion - if ($oldmetatype == 'X' && $xmldb_field->GetType() == XMLDB_TYPE_INTEGER) { - $results[] = 'UPDATE ' . $tablename . ' SET ' . $tempcolname . ' = CAST(' . $this->mdb->sql_compare_text($fieldname) . ' AS INT)'; - } else if ($oldmetatype == 'X' && $xmldb_field->GetType() == XMLDB_TYPE_NUMBER) { - $results[] = 'UPDATE ' . $tablename . ' SET ' . $tempcolname . ' = CAST(' . $this->mdb->sql_compare_text($fieldname) . ' AS NUMBER)'; - - // Normal cases, implicit conversion - } else { - $results[] = 'UPDATE ' . $tablename . ' SET ' . $tempcolname . ' = ' . $fieldname; - } - // Drop the old column - $xmldb_field->setName($fieldname); //Set back the original field name - $results = array_merge($results, $this->getDropFieldSQL($xmldb_table, $xmldb_field)); - // Rename the temp column to the original one - $results[] = 'ALTER TABLE ' . $tablename . ' RENAME COLUMN ' . $tempcolname . ' TO ' . $fieldname; - // Mark we have performed one change based in temp fields - $from_temp_fields = true; - // Re-enable the notnull and default sections so the general AlterFieldSQL can use it - $skip_notnull_clause = false; - $skip_default_clause = false; - // Disable the type section because we have done it with the temp field - $skip_type_clause = true; - // If new field is nullable, nullability hasn't changed - if (!$xmldb_field->getNotnull()) { - $notnullchanged = false; - } - // If new field hasn't default, default hasn't changed - if ($xmldb_field->getDefault() === null) { - $defaultchanged = false; - } - } - - // If type and precision and decimals hasn't changed, prevent the type clause - if (!$typechanged && !$precisionchanged && !$decimalchanged) { - $skip_type_clause = true; - } - - // If NULL/NOT NULL hasn't changed - // prevent null clause to be specified - if (!$notnullchanged) { - $skip_notnull_clause = true; // Initially, prevent the notnull clause - // But, if we have used the temp field and the new field is not null, then enforce the not null clause - if ($from_temp_fields && $xmldb_field->getNotnull()) { - $skip_notnull_clause = false; - } - } - // If default hasn't changed - // prevent default clause to be specified - if (!$defaultchanged) { - $skip_default_clause = true; // Initially, prevent the default clause - // But, if we have used the temp field and the new field has default clause, then enforce the default clause - if ($from_temp_fields) { - $default_clause = $this->getDefaultClause($xmldb_field); - if ($default_clause) { - $skip_notnull_clause = false; - } - } - } - - // If arriving here, something is not being skipped (type, notnull, default), calculate the standard AlterFieldSQL - if (!$skip_type_clause || !$skip_notnull_clause || !$skip_default_clause) { - $results = array_merge($results, parent::getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause, $skip_default_clause, $skip_notnull_clause)); - return $results; - } - - // Finally return results - return $results; - } - - /** - * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default - * (usually invoked from getModifyDefaultSQL() - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @param xmldb_field $xmldb_field The xmldb_field object instance. - * @return array Array of SQL statements to create a field's default. - */ - public function getCreateDefaultSQL($xmldb_table, $xmldb_field) { - // Just a wrapper over the getAlterFieldSQL() function for Oracle that - // is capable of handling defaults - return $this->getAlterFieldSQL($xmldb_table, $xmldb_field); - } - - /** - * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default - * (usually invoked from getModifyDefaultSQL() - * - * Note that this method may be dropped in future. - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @param xmldb_field $xmldb_field The xmldb_field object instance. - * @return array Array of SQL statements to create a field's default. - * - * @todo MDL-31147 Moodle 2.1 - Drop getDropDefaultSQL() - */ - public function getDropDefaultSQL($xmldb_table, $xmldb_field) { - // Just a wrapper over the getAlterFieldSQL() function for Oracle that - // is capable of handling defaults - return $this->getAlterFieldSQL($xmldb_table, $xmldb_field); - } - - /** - * Given one xmldb_table returns one string with the sequence of the table - * in the table (fetched from DB) - * The sequence name for oracle is calculated by looking the corresponding - * trigger and retrieving the sequence name from it (because sequences are - * independent elements) - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @return string|bool If no sequence is found, returns false - */ - public function getSequenceFromDB($xmldb_table) { - - $tablename = strtoupper($this->getTableName($xmldb_table)); - $prefixupper = strtoupper($this->prefix); - $sequencename = false; - - if ($trigger = $this->mdb->get_record_sql("SELECT trigger_name, trigger_body - FROM user_triggers - WHERE table_name = ? AND trigger_name LIKE ?", - array($tablename, "{$prefixupper}%_ID%_TRG"))) { - // If trigger found, regexp it looking for the sequence name - preg_match('/.*SELECT (.*)\.nextval/i', $trigger->trigger_body, $matches); - if (isset($matches[1])) { - $sequencename = $matches[1]; - } - } - - return $sequencename; - } - - /** - * Given one xmldb_table returns one string with the trigger - * in the table (fetched from DB) - * - * @param xmldb_table $xmldb_table The xmldb_table object instance. - * @return string|bool If no trigger is found, returns false - */ - public function getTriggerFromDB($xmldb_table) { - - $tablename = strtoupper($this->getTableName($xmldb_table)); - $prefixupper = strtoupper($this->prefix); - $triggername = false; - - if ($trigger = $this->mdb->get_record_sql("SELECT trigger_name, trigger_body - FROM user_triggers - WHERE table_name = ? AND trigger_name LIKE ?", - array($tablename, "{$prefixupper}%_ID%_TRG"))) { - $triggername = $trigger->trigger_name; - } - - return $triggername; - } - - /** - * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg). - * - * (MySQL requires the whole xmldb_table object to be specified, so we add it always) - * - * This is invoked from getNameForObject(). - * Only some DB have this implemented. - * - * @param string $object_name The object's name to check for. - * @param string $type The object's type (pk, uk, fk, ck, ix, uix, seq, trg). - * @param string $table_name The table's name to check in - * @return bool If such name is currently in use (true) or no (false) - */ - public function isNameInUse($object_name, $type, $table_name) { - switch($type) { - case 'ix': - case 'uix': - case 'seq': - case 'trg': - if ($check = $this->mdb->get_records_sql("SELECT object_name - FROM user_objects - WHERE lower(object_name) = ?", array(strtolower($object_name)))) { - return true; - } - break; - case 'pk': - case 'uk': - case 'fk': - case 'ck': - if ($check = $this->mdb->get_records_sql("SELECT constraint_name - FROM user_constraints - WHERE lower(constraint_name) = ?", array(strtolower($object_name)))) { - return true; - } - break; - } - return false; //No name in use found - } - - /** - * Adds slashes to string. - * @param string $s - * @return string The escaped string. - */ - public function addslashes($s) { - // do not use php addslashes() because it depends on PHP quote settings! - $s = str_replace("'", "''", $s); - return $s; - } - - /** - * Returns an array of reserved words (lowercase) for this DB - * @return array An array of database specific reserved words - */ - public static function getReservedWords() { - // This file contains the reserved words for Oracle databases - // from http://download-uk.oracle.com/docs/cd/B10501_01/server.920/a96540/ap_keywd.htm - $reserved_words = array ( - 'access', 'add', 'all', 'alter', 'and', 'any', - 'as', 'asc', 'audit', 'between', 'by', 'char', - 'check', 'cluster', 'column', 'comment', - 'compress', 'connect', 'create', 'current', - 'date', 'decimal', 'default', 'delete', 'desc', - 'distinct', 'drop', 'else', 'exclusive', 'exists', - 'file', 'float', 'for', 'from', 'grant', 'group', - 'having', 'identified', 'immediate', 'in', - 'increment', 'index', 'initial', 'insert', - 'integer', 'intersect', 'into', 'is', 'level', - 'like', 'lock', 'long', 'maxextents', 'minus', - 'mlslabel', 'mode', 'modify', 'nchar', 'nclob', 'noaudit', - 'nocompress', 'not', 'nowait', 'null', 'number', 'nvarchar2', - 'of', 'offline', 'on', 'online', 'option', 'or', - 'order', 'pctfree', 'prior', 'privileges', - 'public', 'raw', 'rename', 'resource', 'revoke', - 'row', 'rowid', 'rownum', 'rows', 'select', - 'session', 'set', 'share', 'size', 'smallint', - 'start', 'successful', 'synonym', 'sysdate', - 'table', 'then', 'to', 'trigger', 'uid', 'union', - 'unique', 'update', 'user', 'validate', 'values', - 'varchar', 'varchar2', 'view', 'whenever', - 'where', 'with' - ); - return $reserved_words; - } -} diff --git a/lib/ddl/sql_generator.php b/lib/ddl/sql_generator.php index 937f127eb07..2dbf27af6d2 100644 --- a/lib/ddl/sql_generator.php +++ b/lib/ddl/sql_generator.php @@ -1393,7 +1393,7 @@ abstract class sql_generator { public static function getAllReservedWords() { global $CFG; - $generators = array('mysql', 'postgres', 'oracle', 'mssql'); + $generators = array('mysql', 'postgres', 'mssql'); $reserved_words = array(); foreach($generators as $generator) { diff --git a/lib/ddl/tests/ddl_test.php b/lib/ddl/tests/ddl_test.php index a1e4eae12be..1750e3b4a71 100644 --- a/lib/ddl/tests/ddl_test.php +++ b/lib/ddl/tests/ddl_test.php @@ -2131,7 +2131,6 @@ final class ddl_test extends \database_driver_testcase { $rec = $DB->get_record($tablename, array('id'=>$id)); $this->assertSame($maxstr, $rec->name); - // Following test is supposed to fail in oracle. $maxstr = ''; for ($i=0; $iget_record($tablename, array('id'=>$id)); $this->assertSame($maxstr, $rec->name); } catch (dml_exception $e) { - if ($DB->get_dbfamily() === 'oracle') { - $this->fail('Oracle does not support text fields larger than 4000 bytes, this is not a big problem for mostly ascii based languages'); - } else { - throw $e; - } + throw $e; } $table = new xmldb_table('testtable'); @@ -2298,7 +2293,6 @@ final class ddl_test extends \database_driver_testcase { $this->assertSame("`$columnname`", $gen->getEncQuoted($columnname)); break; case 'mssql': // The Moodle connection runs under 'QUOTED_IDENTIFIER ON'. - case 'oracle': case 'postgres': case 'sqlite': default: @@ -2360,7 +2354,6 @@ final class ddl_test extends \database_driver_testcase { $gen->getRenameFieldSQL($table, $field, $newcolumnname) ); break; - case 'oracle': case 'postgres': default: $this->assertSame( @@ -2388,7 +2381,6 @@ final class ddl_test extends \database_driver_testcase { $gen->getRenameFieldSQL($table, $field, $newcolumnname) ); break; - case 'oracle': case 'postgres': default: $this->assertSame( diff --git a/lib/dml/auroramysql_native_moodle_database.php b/lib/dml/auroramysql_native_moodle_database.php index 3000913847a..88e0c83a78b 100644 --- a/lib/dml/auroramysql_native_moodle_database.php +++ b/lib/dml/auroramysql_native_moodle_database.php @@ -75,7 +75,7 @@ class auroramysql_native_moodle_database extends mysqli_native_moodle_database { * Returns more specific database driver type * * Returns more specific database driver type. Can be used before connect(). - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string db type mysqli, pgsql, mssql, sqlsrv */ protected function get_dbtype(): ?string { return 'auroramysql'; diff --git a/lib/dml/mariadb_native_moodle_database.php b/lib/dml/mariadb_native_moodle_database.php index 66d24d09ebd..d12aa0df8f1 100644 --- a/lib/dml/mariadb_native_moodle_database.php +++ b/lib/dml/mariadb_native_moodle_database.php @@ -68,7 +68,7 @@ class mariadb_native_moodle_database extends mysqli_native_moodle_database { /** * Returns more specific database driver type * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string db type mysqli, pgsql, mssql, sqlsrv */ protected function get_dbtype() { return 'mariadb'; diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index e0ae9acd5cd..3bfc69971ca 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -188,7 +188,7 @@ abstract class moodle_database { * * The loaded class is within lib/dml directory and of the form: $type.'_'.$library.'_moodle_database' * - * @param string $type Database driver's type. (eg: mysqli, pgsql, mssql, sqldrv, oci, etc.) + * @param string $type Database driver's type. (eg: mysqli, pgsql, mssql, sqldrv, etc.) * @param string $library Database driver's library (native, pdo, etc.) * @param bool $external True if this is an external database. * @return ?moodle_database driver object or null if error, for example of driver object see {@see mysqli_native_moodle_database} @@ -219,14 +219,14 @@ abstract class moodle_database { /** * Returns the database family type. (This sort of describes the SQL 'dialect') * Note: can be used before connect() - * @return string The db family name (mysql, postgres, mssql, oracle, etc.) + * @return string The db family name (mysql, postgres, mssql, etc.) */ abstract public function get_dbfamily(); /** * Returns a more specific database driver type * Note: can be used before connect() - * @return string The db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string The db type mysqli, pgsql, mssql, sqlsrv */ abstract protected function get_dbtype(); diff --git a/lib/dml/mysqli_native_moodle_database.php b/lib/dml/mysqli_native_moodle_database.php index 45bd69d463c..21c9c93b65a 100644 --- a/lib/dml/mysqli_native_moodle_database.php +++ b/lib/dml/mysqli_native_moodle_database.php @@ -135,7 +135,7 @@ class mysqli_native_moodle_database extends moodle_database { /** * Returns database family type - describes SQL dialect * Note: can be used before connect() - * @return string db family name (mysql, postgres, mssql, oracle, etc.) + * @return string db family name (mysql, postgres, mssql, etc.) */ public function get_dbfamily() { return 'mysql'; @@ -144,7 +144,7 @@ class mysqli_native_moodle_database extends moodle_database { /** * Returns more specific database driver type * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string db type mysqli, pgsql, mssql, sqlsrv */ protected function get_dbtype() { return 'mysqli'; diff --git a/lib/dml/oci_native_moodle_database.php b/lib/dml/oci_native_moodle_database.php deleted file mode 100644 index 94625934994..00000000000 --- a/lib/dml/oci_native_moodle_database.php +++ /dev/null @@ -1,1886 +0,0 @@ -. - -/** - * Native oci class representing moodle database interface. - * - * @package core_dml - * @copyright 2008 Petr Skoda (http://skodak.org) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__.'/moodle_database.php'); -require_once(__DIR__.'/oci_native_moodle_recordset.php'); -require_once(__DIR__.'/oci_native_moodle_temptables.php'); - -/** - * Native oci class representing moodle database interface. - * - * One complete reference for PHP + OCI: - * http://www.oracle.com/technology/tech/php/underground-php-oracle-manual.html - * - * @package core_dml - * @copyright 2008 Petr Skoda (http://skodak.org) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class oci_native_moodle_database extends moodle_database { - - protected $oci = null; - - /** @var To store stmt errors and enable get_last_error() to detect them.*/ - private $last_stmt_error = null; - /** @var Default value initialised in connect method, we need the driver to be present.*/ - private $commit_status = null; - - /** @var null|int To handle oci driver default verbosity.*/ - private $last_error_reporting; - /** @var To store unique_session_id. Needed for temp tables unique naming.*/ - private $unique_session_id; - - /** - * Detects if all needed PHP stuff installed. - * Note: can be used before connect() - * @return mixed true if ok, string if something - */ - public function driver_installed() { - if (!extension_loaded('oci8')) { - return get_string('ociextensionisnotpresentinphp', 'install'); - } - return true; - } - - /** - * Returns database family type - describes SQL dialect - * Note: can be used before connect() - * @return string db family name (mysql, postgres, mssql, oracle, etc.) - */ - public function get_dbfamily() { - return 'oracle'; - } - - /** - * Returns more specific database driver type - * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv - */ - protected function get_dbtype() { - return 'oci'; - } - - /** - * Returns general database library name - * Note: can be used before connect() - * @return string db type pdo, native - */ - protected function get_dblibrary() { - return 'native'; - } - - /** - * Returns localised database type name - * Note: can be used before connect() - * @return string - */ - public function get_name() { - return get_string('nativeoci', 'install'); - } - - /** - * Returns localised database configuration help. - * Note: can be used before connect() - * @return string - */ - public function get_configuration_help() { - return get_string('nativeocihelp', 'install'); - } - - /** - * Diagnose database and tables, this function is used - * to verify database and driver settings, db engine types, etc. - * - * @return string null means everything ok, string means problem found. - */ - public function diagnose() { - return null; - } - - /** - * Connect to db - * Must be called before other methods. - * @param string $dbhost The database host. - * @param string $dbuser The database username. - * @param string $dbpass The database username's password. - * @param string $dbname The name of the database being connected to. - * @param mixed $prefix string means moodle db prefix, false used for external databases where prefix not used - * @param array $dboptions driver specific options - * @return bool true - * @throws dml_connection_exception if error - */ - public function connect($dbhost, $dbuser, $dbpass, $dbname, $prefix, ?array $dboptions=null) { - if ($prefix == '' and !$this->external) { - //Enforce prefixes for everybody but mysql - throw new dml_exception('prefixcannotbeempty', $this->get_dbfamily()); - } - - $driverstatus = $this->driver_installed(); - - if ($driverstatus !== true) { - throw new dml_exception('dbdriverproblem', $driverstatus); - } - - // Autocommit ON by default. - // Switching to OFF (OCI_DEFAULT), when playing with transactions - // please note this thing is not defined if oracle driver not present in PHP - // which means it can not be used as default value of object property! - $this->commit_status = OCI_COMMIT_ON_SUCCESS; - - $this->store_settings($dbhost, $dbuser, $dbpass, $dbname, $prefix, $dboptions); - unset($this->dboptions['dbsocket']); - - // NOTE: use of ', ", / and \ is very problematic, even native oracle tools seem to have - // problems with these, so just forget them and do not report problems into tracker... - - if (empty($this->dbhost)) { - // old style full address (TNS) - $dbstring = $this->dbname; - } else { - if (empty($this->dboptions['dbport'])) { - $this->dboptions['dbport'] = 1521; - } - $dbstring = '//'.$this->dbhost.':'.$this->dboptions['dbport'].'/'.$this->dbname; - } - - ob_start(); - if (empty($this->dboptions['dbpersist'])) { - $this->oci = oci_new_connect($this->dbuser, $this->dbpass, $dbstring, 'AL32UTF8'); - } else { - $this->oci = oci_pconnect($this->dbuser, $this->dbpass, $dbstring, 'AL32UTF8'); - } - $dberr = ob_get_contents(); - ob_end_clean(); - - - if ($this->oci === false) { - $this->oci = null; - $e = oci_error(); - if (isset($e['message'])) { - $dberr = $e['message']; - } - throw new dml_connection_exception($dberr); - } - - // Disable logging until we are fully setup. - $this->query_log_prevent(); - - // Make sure moodle package is installed - now required. - if (!$this->oci_package_installed()) { - try { - $this->attempt_oci_package_install(); - } catch (Exception $e) { - // Ignore problems, only the result counts, - // admins have to fix it manually if necessary. - } - if (!$this->oci_package_installed()) { - throw new dml_exception('dbdriverproblem', 'Oracle PL/SQL Moodle support package MOODLELIB is not installed! Database administrator has to execute /lib/dml/oci_native_moodle_package.sql script.'); - } - } - - // get unique session id, to be used later for temp tables stuff - $sql = 'SELECT DBMS_SESSION.UNIQUE_SESSION_ID() FROM DUAL'; - $this->query_start($sql, null, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW); - oci_free_statement($stmt); - $this->unique_session_id = reset($records[0]); - - //note: do not send "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='.,'" ! - // instead fix our PHP code to convert "," to "." properly! - - // We can enable logging now. - $this->query_log_allow(); - - // Connection stabilised and configured, going to instantiate the temptables controller - $this->temptables = new oci_native_moodle_temptables($this, $this->unique_session_id); - - return true; - } - - /** - * Close database connection and release all resources - * and memory (especially circular memory references). - * Do NOT use connect() again, create a new instance if needed. - */ - public function dispose() { - parent::dispose(); // Call parent dispose to write/close session and other common stuff before closing connection - if ($this->oci) { - oci_close($this->oci); - $this->oci = null; - } - } - - - /** - * Called before each db query. - * @param string $sql - * @param array|null $params An array of parameters. - * @param int $type type of query - * @param mixed $extrainfo driver specific extra information - * @return void - */ - protected function query_start($sql, ?array $params, $type, $extrainfo=null) { - parent::query_start($sql, $params, $type, $extrainfo); - // oci driver tents to send debug to output, we do not need that ;-) - $this->last_error_reporting = error_reporting(0); - } - - /** - * Called immediately after each db query. - * @param mixed db specific result - * @return void - */ - protected function query_end($result, $stmt=null) { - // reset original debug level - error_reporting($this->last_error_reporting); - if ($stmt and $result === false) { - // Look for stmt error and store it - if (is_resource($stmt)) { - $e = oci_error($stmt); - if ($e !== false) { - $this->last_stmt_error = $e['message']; - } - } - oci_free_statement($stmt); - } - parent::query_end($result); - } - - /** - * Returns database server info array - * @return array Array containing 'description' and 'version' info - */ - public function get_server_info() { - static $info = null; // TODO: move to real object property - - if (is_null($info)) { - $this->query_start("--oci_server_version()", null, SQL_QUERY_AUX); - $description = oci_server_version($this->oci); - $this->query_end(true); - preg_match('/(\d+\.)+\d+/', $description, $matches); - $info = array('description'=>$description, 'version'=>$matches[0]); - } - - return $info; - } - - /** - * Converts short table name {tablename} to real table name - * supporting temp tables ($this->unique_session_id based) if detected - * - * @param string sql - * @return string sql - */ - protected function fix_table_names($sql) { - if (preg_match_all('/\{([a-z][a-z0-9_]*)\}/', $sql, $matches)) { - foreach($matches[0] as $key=>$match) { - $name = $matches[1][$key]; - if ($this->temptables && $this->temptables->is_temptable($name)) { - $sql = str_replace($match, $this->temptables->get_correct_name($name), $sql); - } else { - $sql = str_replace($match, $this->prefix.$name, $sql); - } - } - } - return $sql; - } - - /** - * Returns supported query parameter types - * @return int bitmask of accepted SQL_PARAMS_* - */ - protected function allowed_param_types() { - return SQL_PARAMS_NAMED; - } - - /** - * Returns last error reported by database engine. - * @return string error message - */ - public function get_last_error() { - $error = false; - // First look for any previously saved stmt error - if (!empty($this->last_stmt_error)) { - $error = $this->last_stmt_error; - $this->last_stmt_error = null; - } else { // Now try connection error - $e = oci_error($this->oci); - if ($e !== false) { - $error = $e['message']; - } - } - return $error; - } - - /** - * Prepare the statement for execution - * - * @param string $sql - * @return resource - * - * @throws dml_exception - */ - protected function parse_query($sql) { - $stmt = oci_parse($this->oci, $sql); - if ($stmt == false) { - throw new dml_exception('dmlparseexception', null, $this->get_last_error()); - } - return $stmt; - } - - /** - * Make sure there are no reserved words in param names... - * @param string $sql - * @param array $params - * @return array ($sql, $params) updated query and parameters - */ - protected function tweak_param_names($sql, array $params) { - global $CFG; - - require_once($CFG->libdir . '/ddllib.php'); - - if (empty($params)) { - return array($sql, $params); - } - - $newparams = array(); - $searcharr = array(); // search => replace pairs - foreach ($params as $name => $value) { - // Keep the name within the xmldb_field::NAME_MAX_LENGTH chars limit always (prefixing/replacing). - if (strlen($name) <= (xmldb_field::NAME_MAX_LENGTH - 2)) { - $newname = 'o_' . $name; - } else { - $newname = 'o_' . substr($name, 2); - } - $newparams[$newname] = $value; - $searcharr[':' . $name] = ':' . $newname; - } - // sort by length desc to avoid potential str_replace() overlap - uksort($searcharr, array('oci_native_moodle_database', 'compare_by_length_desc')); - - $sql = str_replace(array_keys($searcharr), $searcharr, $sql); - return array($sql, $newparams); - } - - /** - * 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 ($usecache and $this->tables !== null) { - return $this->tables; - } - $this->tables = array(); - $prefix = str_replace('_', "\\_", strtoupper($this->prefix)); - $sql = "SELECT TABLE_NAME - FROM CAT - WHERE TABLE_TYPE='TABLE' - AND TABLE_NAME NOT LIKE 'BIN\$%' - AND TABLE_NAME LIKE '$prefix%' ESCAPE '\\'"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_ASSOC); - oci_free_statement($stmt); - $records = array_map('strtolower', $records['TABLE_NAME']); - foreach ($records as $tablename) { - if ($this->prefix !== false && $this->prefix !== '') { - if (strpos($tablename, $this->prefix) !== 0) { - continue; - } - $tablename = substr($tablename, strlen($this->prefix)); - } - $this->tables[$tablename] = $tablename; - } - - // Add the currently available temptables - $this->tables = array_merge($this->tables, $this->temptables->get_temptables()); - - return $this->tables; - } - - /** - * Return table indexes - everything lowercased. - * @param string $table The table we want to get indexes from. - * @return array An associative array of indexes containing 'unique' flag and 'columns' being indexed - */ - public function get_indexes($table) { - $indexes = array(); - $tablename = strtoupper($this->prefix.$table); - - $sql = "SELECT i.INDEX_NAME, i.INDEX_TYPE, i.UNIQUENESS, c.COLUMN_POSITION, c.COLUMN_NAME, e.COLUMN_EXPRESSION, ac.CONSTRAINT_TYPE - FROM ALL_INDEXES i - JOIN ALL_IND_COLUMNS c ON c.INDEX_NAME=i.INDEX_NAME - LEFT JOIN ALL_IND_EXPRESSIONS e ON (e.INDEX_NAME = c.INDEX_NAME AND e.COLUMN_POSITION = c.COLUMN_POSITION) - LEFT JOIN ALL_CONSTRAINTS ac ON (ac.TABLE_NAME=i.TABLE_NAME AND ac.CONSTRAINT_NAME=i.INDEX_NAME AND ac.CONSTRAINT_TYPE='P') - WHERE i.TABLE_NAME = '$tablename' - ORDER BY i.INDEX_NAME, c.COLUMN_POSITION"; - - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW); - oci_free_statement($stmt); - - foreach ($records as $record) { - if ($record['CONSTRAINT_TYPE'] === 'P') { - //ignore for now; - continue; - } - $indexname = strtolower($record['INDEX_NAME']); - if (!isset($indexes[$indexname])) { - $indexes[$indexname] = array('primary' => ($record['CONSTRAINT_TYPE'] === 'P'), - 'unique' => ($record['UNIQUENESS'] === 'UNIQUE'), - 'columns' => array()); - } - - // If this is an unique, function-based, index, then we have to look to the expression - // and calculate the column name by parsing it. - if ($record['UNIQUENESS'] === 'UNIQUE' && $record['INDEX_TYPE'] === 'FUNCTION-BASED NORMAL') { - // Only if there is an expression to look. - if (!empty($record['COLUMN_EXPRESSION'])) { - // Let's parse the usual code used for these unique indexes. - $regex = '/^CASE *WHEN .* THEN "(?[^"]+)" ELSE NULL END *$/'; - if (preg_match($regex, $record['COLUMN_EXPRESSION'], $matches)) { - $record['COLUMN_NAME'] = $matches['column_name'] ?? $record['COLUMN_NAME']; - } - } - } - - $indexes[$indexname]['columns'][] = strtolower($record['COLUMN_NAME']); - } - - return $indexes; - } - - /** - * Fetches detailed information about columns in table. - * - * @param string $table name - * @return array array of database_column_info objects indexed with column names - */ - protected function fetch_columns(string $table): array { - $structure = array(); - - // We give precedence to CHAR_LENGTH for VARCHAR2 columns over WIDTH because the former is always - // BYTE based and, for cross-db operations, we want CHAR based results. See MDL-29415 - // Instead of guessing sequence based exclusively on name, check tables against user_triggers to - // ensure the table has a 'before each row' trigger to assume 'id' is auto_increment. MDL-32365 - $sql = "SELECT CNAME, COLTYPE, nvl(CHAR_LENGTH, WIDTH) AS WIDTH, SCALE, PRECISION, NULLS, DEFAULTVAL, - DECODE(NVL(TRIGGER_NAME, '0'), '0', '0', '1') HASTRIGGER - FROM COL c - LEFT JOIN USER_TAB_COLUMNS u ON (u.TABLE_NAME = c.TNAME AND u.COLUMN_NAME = c.CNAME AND u.DATA_TYPE = 'VARCHAR2') - LEFT JOIN USER_TRIGGERS t ON (t.TABLE_NAME = c.TNAME AND TRIGGER_TYPE = 'BEFORE EACH ROW' AND c.CNAME = 'ID') - WHERE TNAME = UPPER('{" . $table . "}') - ORDER BY COLNO"; - - list($sql, $params, $type) = $this->fix_sql_params($sql, null); - - $this->query_start($sql, null, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW); - oci_free_statement($stmt); - - if (!$records) { - return array(); - } - foreach ($records as $rawcolumn) { - $rawcolumn = (object)$rawcolumn; - - $info = new stdClass(); - $info->name = strtolower($rawcolumn->CNAME); - $info->auto_increment = ((int)$rawcolumn->HASTRIGGER) ? true : false; - $matches = null; - - if ($rawcolumn->COLTYPE === 'VARCHAR2' - or $rawcolumn->COLTYPE === 'VARCHAR' - or $rawcolumn->COLTYPE === 'NVARCHAR2' - or $rawcolumn->COLTYPE === 'NVARCHAR' - or $rawcolumn->COLTYPE === 'CHAR' - or $rawcolumn->COLTYPE === 'NCHAR') { - $info->type = $rawcolumn->COLTYPE; - $info->meta_type = 'C'; - $info->max_length = $rawcolumn->WIDTH; - $info->scale = null; - $info->not_null = ($rawcolumn->NULLS === 'NOT NULL'); - $info->has_default = !is_null($rawcolumn->DEFAULTVAL); - if ($info->has_default) { - - // this is hacky :-( - if ($rawcolumn->DEFAULTVAL === 'NULL') { - $info->default_value = null; - } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space - $info->default_value = ""; - } else if ($rawcolumn->DEFAULTVAL === "' '") { // Sometimes it's stored without trailing space - $info->default_value = ""; - } else { - $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space - $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim '' - } - } else { - $info->default_value = null; - } - $info->primary_key = false; - $info->binary = false; - $info->unsigned = null; - $info->unique = null; - - } else if ($rawcolumn->COLTYPE === 'NUMBER') { - $info->type = $rawcolumn->COLTYPE; - $info->max_length = $rawcolumn->PRECISION; - $info->binary = false; - if (!is_null($rawcolumn->SCALE) && $rawcolumn->SCALE == 0) { // null in oracle scale allows decimals => not integer - // integer - if ($info->name === 'id') { - $info->primary_key = true; - $info->meta_type = 'R'; - $info->unique = true; - $info->has_default = false; - } else { - $info->primary_key = false; - $info->meta_type = 'I'; - $info->unique = null; - } - $info->scale = 0; - - } else { - //float - $info->meta_type = 'N'; - $info->primary_key = false; - $info->unsigned = null; - $info->unique = null; - $info->scale = $rawcolumn->SCALE; - } - $info->not_null = ($rawcolumn->NULLS === 'NOT NULL'); - $info->has_default = !is_null($rawcolumn->DEFAULTVAL); - if ($info->has_default) { - $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space - } else { - $info->default_value = null; - } - - } else if ($rawcolumn->COLTYPE === 'FLOAT') { - $info->type = $rawcolumn->COLTYPE; - $info->max_length = (int)($rawcolumn->PRECISION * 3.32193); - $info->primary_key = false; - $info->meta_type = 'N'; - $info->unique = null; - $info->not_null = ($rawcolumn->NULLS === 'NOT NULL'); - $info->has_default = !is_null($rawcolumn->DEFAULTVAL); - if ($info->has_default) { - $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space - } else { - $info->default_value = null; - } - - } else if ($rawcolumn->COLTYPE === 'CLOB' - or $rawcolumn->COLTYPE === 'NCLOB') { - $info->type = $rawcolumn->COLTYPE; - $info->meta_type = 'X'; - $info->max_length = -1; - $info->scale = null; - $info->scale = null; - $info->not_null = ($rawcolumn->NULLS === 'NOT NULL'); - $info->has_default = !is_null($rawcolumn->DEFAULTVAL); - if ($info->has_default) { - // this is hacky :-( - if ($rawcolumn->DEFAULTVAL === 'NULL') { - $info->default_value = null; - } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space - $info->default_value = ""; - } else if ($rawcolumn->DEFAULTVAL === "' '") { // Other times it's stored without trailing space - $info->default_value = ""; - } else { - $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space - $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim '' - } - } else { - $info->default_value = null; - } - $info->primary_key = false; - $info->binary = false; - $info->unsigned = null; - $info->unique = null; - - } else if ($rawcolumn->COLTYPE === 'BLOB') { - $info->type = $rawcolumn->COLTYPE; - $info->meta_type = 'B'; - $info->max_length = -1; - $info->scale = null; - $info->scale = null; - $info->not_null = ($rawcolumn->NULLS === 'NOT NULL'); - $info->has_default = !is_null($rawcolumn->DEFAULTVAL); - if ($info->has_default) { - // this is hacky :-( - if ($rawcolumn->DEFAULTVAL === 'NULL') { - $info->default_value = null; - } else if ($rawcolumn->DEFAULTVAL === "' ' ") { // Sometimes it's stored with trailing space - $info->default_value = ""; - } else if ($rawcolumn->DEFAULTVAL === "' '") { // Sometimes it's stored without trailing space - $info->default_value = ""; - } else { - $info->default_value = trim($rawcolumn->DEFAULTVAL); // remove trailing space - $info->default_value = substr($info->default_value, 1, strlen($info->default_value)-2); //trim '' - } - } else { - $info->default_value = null; - } - $info->primary_key = false; - $info->binary = true; - $info->unsigned = null; - $info->unique = null; - - } else { - // unknown type - sorry - $info->type = $rawcolumn->COLTYPE; - $info->meta_type = '?'; - } - - $structure[$info->name] = new database_column_info($info); - } - - return $structure; - } - - /** - * Normalise values based in RDBMS dependencies (booleans, LOBs...) - * - * @param database_column_info $column column metadata corresponding with the value we are going to normalise - * @param mixed $value value we are going to normalise - * @return mixed the normalised value - */ - protected function normalise_value($column, $value) { - $this->detect_objects($value); - - if (is_bool($value)) { // Always, convert boolean to int - $value = (int)$value; - - } else if ($column->meta_type == 'B' && !is_null($value)) { - // Not null BLOB detected, we return 'blob' array instead for later handing on binding. - $value = array('blob' => $value); - - } else if ($column->meta_type == 'X' && !is_null($value) && strlen($value) > 4000) { - // Not null CLOB detected (>4000 optimisation), we return 'clob' array instead for later handing on binding. - $value = array('clob' => (string)$value); - - } else if ($value === '') { - if ($column->meta_type == 'I' or $column->meta_type == 'F' or $column->meta_type == 'N') { - $value = 0; // prevent '' problems in numeric fields - } - } - return $value; - } - - /** - * This function will handle all the column values before being inserted/updated to DB for Oracle - * installations. This is because the "special feature" of Oracle where the empty string is - * equal to NULL and this presents a problem with all our currently NOT NULL default '' fields. - * (and with empties handling in general) - * - * Note that this function is 100% private and should be used, exclusively by DML functions - * in this file. Also, this is considered a DIRTY HACK to be removed when possible. - * - * This function is private and must not be used outside this driver at all - * - * @param $table string the table where the record is going to be inserted/updated (without prefix) - * @param $field string the field where the record is going to be inserted/updated - * @param $value mixed the value to be inserted/updated - */ - private function oracle_dirty_hack($table, $field, $value) { - - // General bound parameter, just hack the spaces and pray it will work. - if (!$table) { - if ($value === '') { - return ' '; - } else if (is_bool($value)) { - return (int)$value; - } else { - return $value; - } - } - - // Get metadata - $columns = $this->get_columns($table); - if (!isset($columns[$field])) { - if ($value === '') { - return ' '; - } else if (is_bool($value)) { - return (int)$value; - } else { - return $value; - } - } - $column = $columns[$field]; - - // !! This paragraph explains behaviour before Moodle 2.0: - // - // For Oracle DB, empty strings are converted to NULLs in DB - // and this breaks a lot of NOT NULL columns currently Moodle. In the future it's - // planned to move some of them to NULL, if they must accept empty values and this - // piece of code will become less and less used. But, for now, we need it. - // What we are going to do is to examine all the data being inserted and if it's - // an empty string (NULL for Oracle) and the field is defined as NOT NULL, we'll modify - // such data in the best form possible ("0" for booleans and numbers and " " for the - // rest of strings. It isn't optimal, but the only way to do so. - // In the opposite, when retrieving records from Oracle, we'll decode " " back to - // empty strings to allow everything to work properly. DIRTY HACK. - - // !! These paragraphs explain the rationale about the change for Moodle 2.5: - // - // Before Moodle 2.0, we only used to apply this DIRTY HACK to NOT NULL columns, as - // stated above, but it causes one problem in NULL columns where both empty strings - // and real NULLs are stored as NULLs, being impossible to differentiate them when - // being retrieved from DB. - // - // So, starting with Moodle 2.0, we are going to apply the DIRTY HACK to all the - // CHAR/CLOB columns no matter of their nullability. That way, when retrieving - // NULLABLE fields we'll get proper empties and NULLs differentiated, so we'll be able - // to rely in NULL/empty/content contents without problems, until now that wasn't - // possible at all. - // - // One space DIRTY HACK is now applied automatically for all query parameters - // and results. The only problem is string concatenation where the glue must - // be specified as "' '" sql fragment. - // - // !! Conclusions: - // - // From Moodle 2.5 onwards, ALL empty strings in Oracle DBs will be stored as - // 1-whitespace char, ALL NULLs as NULLs and, obviously, content as content. And - // those 1-whitespace chars will be converted back to empty strings by all the - // get_field/record/set() functions transparently and any SQL needing direct handling - // of empties will have to use placeholders or sql_isempty() helper function. - - // If the field isn't VARCHAR or CLOB, skip - if ($column->meta_type != 'C' and $column->meta_type != 'X') { - return $value; - } - - // If the value isn't empty, skip - if (!empty($value)) { - return $value; - } - - // Now, we have one empty value, going to be inserted to one VARCHAR2 or CLOB field - // Try to get the best value to be inserted - - // The '0' string doesn't need any transformation, skip - if ($value === '0') { - return $value; - } - - // Transformations start - if (gettype($value) == 'boolean') { - return '0'; // Transform false to '0' that evaluates the same for PHP - - } else if (gettype($value) == 'integer') { - return '0'; // Transform 0 to '0' that evaluates the same for PHP - - } else if ($value === '') { - return ' '; // Transform '' to ' ' that DON'T EVALUATE THE SAME - // (we'll transform back again on get_records_XXX functions and others)!! - } - - // Fail safe to original value - return $value; - } - - /** - * Helper function to order by string length desc - * - * @param $a string first element to compare - * @param $b string second element to compare - * @return int < 0 $a goes first (is less), 0 $b goes first, 0 doesn't matter - */ - private function compare_by_length_desc($a, $b) { - return strlen($b) - strlen($a); - } - - /** - * Is db in unicode mode? - * @return bool - */ - public function setup_is_unicodedb() { - $sql = "SELECT VALUE - FROM NLS_DATABASE_PARAMETERS - WHERE PARAMETER = 'NLS_CHARACTERSET'"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN); - oci_free_statement($stmt); - - return (isset($records['VALUE'][0]) and $records['VALUE'][0] === 'AL32UTF8'); - } - - /** - * Do NOT use in code, to be used by database_manager only! - * @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) { - $this->get_manager(); // Includes DDL exceptions classes ;-) - $sqls = (array)$sql; - - try { - foreach ($sqls as $sql) { - $this->query_start($sql, null, SQL_QUERY_STRUCTURE); - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - } - } catch (ddl_change_structure_exception $e) { - $this->reset_caches($tablenames); - throw $e; - } - - $this->reset_caches($tablenames); - return true; - } - - protected function bind_params($stmt, ?array &$params=null, $tablename=null, ?array &$descriptors = null) { - if ($params) { - $columns = array(); - if ($tablename) { - $columns = $this->get_columns($tablename); - } - foreach($params as $key => $value) { - // Decouple column name and param name as far as sometimes they aren't the same - if ($key == 'o_newfieldtoset') { // found case where column and key diverge, handle that - $columnname = key($value); // columnname is the key of the array - $params[$key] = $value[$columnname]; // set the proper value in the $params array and - $value = $value[$columnname]; // set the proper value in the $value variable - } else { - $columnname = preg_replace('/^o_/', '', $key); // Default columnname (for DB introspecting is key), but... - } - // Continue processing - // Now, handle already detected LOBs - if (is_array($value)) { // Let's go to bind special cases (lob descriptors) - if (isset($value['clob'])) { - $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB); - if ($descriptors === null) { - throw new coding_exception('moodle_database::bind_params() $descriptors not specified for clob'); - } - $descriptors[] = $lob; - oci_bind_by_name($stmt, $key, $lob, -1, SQLT_CLOB); - $lob->writeTemporary($this->oracle_dirty_hack($tablename, $columnname, $params[$key]['clob']), OCI_TEMP_CLOB); - continue; // Column binding finished, go to next one - } else if (isset($value['blob'])) { - $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB); - if ($descriptors === null) { - throw new coding_exception('moodle_database::bind_params() $descriptors not specified for clob'); - } - $descriptors[] = $lob; - oci_bind_by_name($stmt, $key, $lob, -1, SQLT_BLOB); - $lob->writeTemporary($params[$key]['blob'], OCI_TEMP_BLOB); - continue; // Column binding finished, go to next one - } - } else { - // If, at this point, the param value > 4000 (bytes), let's assume it's a clob - // passed in an arbitrary sql (not processed by normalise_value() ever, - // and let's handle it as such. This will provide proper binding of CLOBs in - // conditions and other raw SQLs not covered by the above function. - if (!is_null($value) && strlen($value) > 4000) { - $lob = oci_new_descriptor($this->oci, OCI_DTYPE_LOB); - if ($descriptors === null) { - throw new coding_exception('moodle_database::bind_params() $descriptors not specified for clob'); - } - $descriptors[] = $lob; - oci_bind_by_name($stmt, $key, $lob, -1, SQLT_CLOB); - $lob->writeTemporary($this->oracle_dirty_hack($tablename, $columnname, $params[$key]), OCI_TEMP_CLOB); - continue; // Param binding finished, go to next one. - } - } - // TODO: Put proper types and length is possible (enormous speedup) - // Arrived here, continue with standard processing, using metadata if possible - if (isset($columns[$columnname])) { - $type = $columns[$columnname]->meta_type; - $maxlength = $columns[$columnname]->max_length; - } else { - $type = '?'; - $maxlength = -1; - } - switch ($type) { - case 'I': - case 'R': - // TODO: Optimise - oci_bind_by_name($stmt, $key, $params[$key]); - break; - - case 'N': - case 'F': - // TODO: Optimise - oci_bind_by_name($stmt, $key, $params[$key]); - break; - - case 'B': - // TODO: Only arrive here if BLOB is null: Bind if so, else exception! - // don't break here - - case 'X': - // TODO: Only arrive here if CLOB is null or <= 4000 cc, else exception - // don't break here - - default: // Bind as CHAR (applying dirty hack) - // TODO: Optimise - $params[$key] = $this->oracle_dirty_hack($tablename, $columnname, $params[$key]); - // Because of PHP7 bug (https://bugs.php.net/bug.php?id=72524) it seems that it's - // impossible to bind NULL values in a reliable way, let's use empty string - // instead in the mean time. - if ($params[$key] === null && version_compare(PHP_VERSION, '7.0.0', '>=')) { - $params[$key] = ''; - } - oci_bind_by_name($stmt, $key, $params[$key]); - } - } - } - return $descriptors; - } - - protected function free_descriptors($descriptors) { - foreach ($descriptors as $descriptor) { - // Because all descriptors used in the driver come from LOB::writeTemporary() calls - // we can safely close them here unconditionally. - $descriptor->close(); - // Free resources. - oci_free_descriptor($descriptor); - } - } - - /** - * This function is used to convert all the Oracle 1-space defaults to the empty string - * like a really DIRTY HACK to allow it to work better until all those NOT NULL DEFAULT '' - * fields will be out from Moodle. - * @param string the string to be converted to '' (empty string) if it's ' ' (one space) - * @param mixed the key of the array in case we are using this function from array_walk, - * defaults to null for other (direct) uses - * @return boolean always true (the converted variable is returned by reference) - */ - public static function onespace2empty(&$item, $key=null) { - $item = ($item === ' ') ? '' : $item; - return true; - } - - /** - * Execute general sql query. Should be used only when no other method suitable. - * Do NOT use this to make changes in db structure, use database_manager methods instead! - * @param string $sql query - * @param array $params query parameters - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function execute($sql, ?array $params=null) { - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - - if (strpos($sql, ';') !== false) { - throw new coding_exception('moodle_database::execute() Multiple sql statements found or bound parameters not used properly in query!'); - } - - list($sql, $params) = $this->tweak_param_names($sql, $params); - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $stmt = $this->parse_query($sql); - $descriptors = array(); - $this->bind_params($stmt, $params, null, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - - return true; - } - - /** - * Get a single database record as an object using a SQL statement. - * - * The SQL statement should normally only return one record. - * It is recommended to use get_records_sql() if more matches possible! - * - * @param string $sql The SQL string you wish to be executed, should normally only return one record. - * @param array $params array of sql parameters - * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; - * IGNORE_MULTIPLE means return first, ignore multiple records found(not recommended); - * MUST_EXIST means throw exception if no record or multiple records found - * @return mixed a fieldset object containing the first matching record, false or exception if error not found depending on mode - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function get_record_sql($sql, ?array $params=null, $strictness=IGNORE_MISSING) { - $strictness = (int)$strictness; - if ($strictness == IGNORE_MULTIPLE) { - // do not limit here - ORA does not like that - $rs = $this->get_recordset_sql($sql, $params); - $result = false; - foreach ($rs as $rec) { - $result = $rec; - break; - } - $rs->close(); - return $result; - } - return parent::get_record_sql($sql, $params, $strictness); - } - - /** - * Get a number of records as a moodle_recordset using a SQL statement. - * - * Since this method is a little less readable, use of it should be restricted to - * code where it's possible there might be large datasets being returned. For known - * small datasets use get_records_sql - it leads to simpler code. - * - * The return type is like: - * @see function get_recordset. - * - * @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, required if $limitnum is set). - * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). - * @return moodle_recordset instance - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function get_recordset_sql($sql, ?array $params=null, $limitfrom=0, $limitnum=0) { - - list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum); - - if ($limitfrom) { - $sql .= " OFFSET $limitfrom ROWS"; - } - if ($limitnum) { - $sql .= " FETCH NEXT $limitnum ROWS ONLY"; - } - - list($rawsql, $params, $type) = $this->fix_sql_params($sql, $params); - - list($rawsql, $params) = $this->tweak_param_names($rawsql, $params); - $this->query_start($rawsql, $params, SQL_QUERY_SELECT); - $stmt = $this->parse_query($rawsql); - $descriptors = array(); - $this->bind_params($stmt, $params, null, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - - return $this->create_recordset($stmt); - } - - protected function create_recordset($stmt) { - return new oci_native_moodle_recordset($stmt); - } - - /** - * Get a number of records as an array of objects using a SQL statement. - * - * Return value is like: - * @see function get_records. - * - * @param string $sql the SQL select query to execute. The first column of this SELECT statement - * must be a unique value (usually the 'id' field), as it will be used as the key of the - * returned array. - * @param array $params array of sql parameters - * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). - * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). - * @return array of objects, or empty array if no records were found - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function get_records_sql($sql, ?array $params=null, $limitfrom=0, $limitnum=0) { - - list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum); - - if ($limitfrom) { - $sql .= " OFFSET $limitfrom ROWS"; - } - if ($limitnum) { - $sql .= " FETCH NEXT $limitnum ROWS ONLY"; - } - - list($rawsql, $params, $type) = $this->fix_sql_params($sql, $params); - - list($rawsql, $params) = $this->tweak_param_names($rawsql, $params); - $this->query_start($rawsql, $params, SQL_QUERY_SELECT); - $stmt = $this->parse_query($rawsql); - $descriptors = array(); - $this->bind_params($stmt, $params, null, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW); - oci_free_statement($stmt); - - $return = array(); - - foreach ($records as $row) { - $row = array_change_key_case($row, CASE_LOWER); - unset($row['oracle_rownum']); - array_walk($row, array('oci_native_moodle_database', 'onespace2empty')); - $id = reset($row); - if (isset($return[$id])) { - $colname = key($row); - debugging("Did you remember to make the first column something unique in your call to get_records? Duplicate value '$id' found in column '$colname'.", DEBUG_DEVELOPER); - } - $return[$id] = (object)$row; - } - - return $return; - } - - /** - * Selects records and return values (first field) as an array using a SQL statement. - * - * @param string $sql The SQL query - * @param array $params array of sql parameters - * @return array of values - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function get_fieldset_sql($sql, ?array $params=null) { - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - - list($sql, $params) = $this->tweak_param_names($sql, $params); - $this->query_start($sql, $params, SQL_QUERY_SELECT); - $stmt = $this->parse_query($sql); - $descriptors = array(); - $this->bind_params($stmt, $params, null, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_COLUMN); - oci_free_statement($stmt); - - $return = reset($records); - array_walk($return, array('oci_native_moodle_database', 'onespace2empty')); - - return $return; - } - - /** - * Insert new record into database, as fast as possible, no safety checks, lobs not supported. - * @param string $table name - * @param mixed $params data record as object or array - * @param bool $returnit return it of inserted record - * @param bool $bulk true means repeated inserts expected - * @param bool $customsequence true if 'id' included in $params, disables $returnid - * @return bool|int true or new id - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function insert_record_raw($table, $params, $returnid=true, $bulk=false, $customsequence=false) { - if (!is_array($params)) { - $params = (array)$params; - } - - $returning = ""; - - if ($customsequence) { - if (!isset($params['id'])) { - throw new coding_exception('moodle_database::insert_record_raw() id field must be specified if custom sequences used.'); - } - $returnid = false; - } else { - unset($params['id']); - if ($returnid) { - $returning = " RETURNING id INTO :oracle_id"; // crazy name nobody is ever going to use or parameter ;-) - } - } - - if (empty($params)) { - throw new coding_exception('moodle_database::insert_record_raw() no fields found.'); - } - - $fields = implode(',', array_keys($params)); - $values = array(); - foreach ($params as $pname => $value) { - $values[] = ":$pname"; - } - $values = implode(',', $values); - - $sql = "INSERT INTO {" . $table . "} ($fields) VALUES ($values)"; - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - $sql .= $returning; - - $id = 0; - - // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080 - // list($sql, $params) = $this->tweak_param_names($sql, $params); - $this->query_start($sql, $params, SQL_QUERY_INSERT); - $stmt = $this->parse_query($sql); - if ($returning) { - oci_bind_by_name($stmt, ":oracle_id", $id, 10, SQLT_INT); - } - $descriptors = array(); - $this->bind_params($stmt, $params, $table, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - - if (!$returnid) { - return true; - } - - if (!$returning) { - die('TODO - implement oracle 9.2 insert support'); //TODO - } - - return (int)$id; - } - - /** - * Insert a record into a table and return the "id" field if required. - * - * Some conversions and safety checks are carried out. Lobs are supported. - * If the return ID isn't required, then this just reports success as true/false. - * $data is an object containing needed data - * @param string $table The database table to be inserted into - * @param object|array $dataobject A data object with values for one or more fields in the record - * @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned. - * @return bool|int true or new id - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function insert_record($table, $dataobject, $returnid=true, $bulk=false) { - $dataobject = (array)$dataobject; - - $columns = $this->get_columns($table); - if (empty($columns)) { - throw new dml_exception('ddltablenotexist', $table); - } - - $cleaned = array(); - - foreach ($dataobject as $field=>$value) { - if ($field === 'id') { - continue; - } - if (!isset($columns[$field])) { // Non-existing table field, skip it - continue; - } - $column = $columns[$field]; - $cleaned[$field] = $this->normalise_value($column, $value); - } - - return $this->insert_record_raw($table, $cleaned, $returnid, $bulk); - } - - /** - * Import a record into a table, id field is required. - * Safety checks are NOT carried out. Lobs are supported. - * - * @param string $table name of database table to be inserted into - * @param object $dataobject A data object with values for one or more fields in the record - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function import_record($table, $dataobject) { - $dataobject = (array)$dataobject; - - $columns = $this->get_columns($table); - $cleaned = array(); - - foreach ($dataobject as $field=>$value) { - if (!isset($columns[$field])) { - continue; - } - $column = $columns[$field]; - $cleaned[$field] = $this->normalise_value($column, $value); - } - - return $this->insert_record_raw($table, $cleaned, false, true, true); - } - - /** - * Update record in database, as fast as possible, no safety checks, lobs not supported. - * @param string $table name - * @param stdClass|array $params data record as object or array - * @param bool true means repeated updates expected - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function update_record_raw($table, $params, $bulk=false) { - $params = (array)$params; - - if (!isset($params['id'])) { - throw new coding_exception('moodle_database::update_record_raw() id field must be specified.'); - } - - if (empty($params)) { - throw new coding_exception('moodle_database::update_record_raw() no fields found.'); - } - - $sets = array(); - foreach ($params as $field=>$value) { - if ($field == 'id') { - continue; - } - $sets[] = "$field = :$field"; - } - - $sets = implode(',', $sets); - $sql = "UPDATE {" . $table . "} SET $sets WHERE id=:id"; - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - - // note we don't need tweak_param_names() here. Placeholders are safe column names. MDL-28080 - // list($sql, $params) = $this->tweak_param_names($sql, $params); - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $stmt = $this->parse_query($sql); - $descriptors = array(); - $this->bind_params($stmt, $params, $table, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - - return true; - } - - /** - * Update a record in a table - * - * $dataobject is an object containing needed data - * Relies on $dataobject having a variable "id" to - * specify the record to update - * - * @param string $table The database table to be checked against. - * @param stdClass|array $dataobject An object with contents equal to fieldname=>fieldvalue. - * Must have an entry for 'id' to map to the table specified. - * @param bool true means repeated updates expected - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function update_record($table, $dataobject, $bulk=false) { - $dataobject = (array)$dataobject; - - $columns = $this->get_columns($table); - $cleaned = array(); - - foreach ($dataobject as $field=>$value) { - if (!isset($columns[$field])) { - continue; - } - $column = $columns[$field]; - $cleaned[$field] = $this->normalise_value($column, $value); - } - - $this->update_record_raw($table, $cleaned, $bulk); - - return true; - } - - /** - * Set a single field in every table record which match a particular WHERE clause. - * - * @param string $table The database table to be checked against. - * @param string $newfield the field to set. - * @param string $newvalue the value to set the field to. - * @param string $select A fragment of SQL to be used in a where clause in the SQL call. - * @param array $params array of sql parameters - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function set_field_select($table, $newfield, $newvalue, $select, ?array $params=null) { - - if ($select) { - $select = "WHERE $select"; - } - if (is_null($params)) { - $params = array(); - } - - // Get column metadata - $columns = $this->get_columns($table); - $column = $columns[$newfield]; - - $newvalue = $this->normalise_value($column, $newvalue); - - list($select, $params, $type) = $this->fix_sql_params($select, $params); - - if (is_bool($newvalue)) { - $newvalue = (int)$newvalue; // prevent "false" problems - } - if (is_null($newvalue)) { - $newsql = "$newfield = NULL"; - } else { - // Set the param to array ($newfield => $newvalue) and key to 'newfieldtoset' - // name in the build sql. Later, bind_params() will detect the value array and - // perform the needed modifications to allow the query to work. Note that - // 'newfieldtoset' is one arbitrary name that hopefully won't be used ever - // in order to avoid problems where the same field is used both in the set clause and in - // the conditions. This was breaking badly in drivers using NAMED params like oci. - $params['newfieldtoset'] = array($newfield => $newvalue); - $newsql = "$newfield = :newfieldtoset"; - } - $sql = "UPDATE {" . $table . "} SET $newsql $select"; - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - - list($sql, $params) = $this->tweak_param_names($sql, $params); - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $stmt = $this->parse_query($sql); - $descriptors = array(); - $this->bind_params($stmt, $params, $table, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - - return true; - } - - /** - * Delete one or more records from a table which match a particular WHERE clause. - * - * @param string $table The database table to be checked against. - * @param string $select A fragment of SQL to be used in a where clause in the SQL call (used to define the selection criteria). - * @param array $params array of sql parameters - * @return bool true - * @throws dml_exception A DML specific exception is thrown for any errors. - */ - public function delete_records_select($table, $select, ?array $params=null) { - - if ($select) { - $select = "WHERE $select"; - } - - $sql = "DELETE FROM {" . $table . "} $select"; - - list($sql, $params, $type) = $this->fix_sql_params($sql, $params); - - list($sql, $params) = $this->tweak_param_names($sql, $params); - $this->query_start($sql, $params, SQL_QUERY_UPDATE); - $stmt = $this->parse_query($sql); - $descriptors = array(); - $this->bind_params($stmt, $params, null, $descriptors); - $result = oci_execute($stmt, $this->commit_status); - $this->free_descriptors($descriptors); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - - return true; - } - - function sql_null_from_clause() { - return ' FROM dual'; - } - - public function sql_bitand($int1, $int2) { - return 'bitand((' . $int1 . '), (' . $int2 . '))'; - } - - public function sql_bitnot($int1) { - return '((0 - (' . $int1 . ')) - 1)'; - } - - public function sql_bitor($int1, $int2) { - return 'MOODLELIB.BITOR(' . $int1 . ', ' . $int2 . ')'; - } - - public function sql_bitxor($int1, $int2) { - return 'MOODLELIB.BITXOR(' . $int1 . ', ' . $int2 . ')'; - } - - /** - * Returns the SQL text to be used in order to perform module '%' - * operation - remainder after division - * - * @param integer int1 first integer in the operation - * @param integer int2 second integer in the operation - * @return string the piece of SQL code to be used in your statement. - */ - public function sql_modulo($int1, $int2) { - return 'MOD(' . $int1 . ', ' . $int2 . ')'; - } - - /** - * Return SQL for casting to char of given field/expression - * - * @param string $field Table field or SQL expression to be cast - * @return string - */ - public function sql_cast_to_char(string $field): string { - return "TO_CHAR({$field})"; - } - - public function sql_cast_char2int($fieldname, $text=false) { - if (!$text) { - return ' CAST(' . $fieldname . ' AS INT) '; - } else { - return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS INT) '; - } - } - - public function sql_cast_char2real($fieldname, $text=false) { - if (!$text) { - return ' CAST(' . $fieldname . ' AS FLOAT) '; - } else { - return ' CAST(' . $this->sql_compare_text($fieldname) . ' AS FLOAT) '; - } - } - - /** - * Returns 'LIKE' part of a query. - * - * @param string $fieldname usually name of the table column - * @param string $param usually bound query parameter (?, :named) - * @param bool $casesensitive use case sensitive search - * @param bool $accensensitive use accent sensitive search (not all databases support accent insensitive) - * @param bool $notlike true means "NOT LIKE" - * @param string $escapechar escape char for '%' and '_' - * @return string SQL code fragment - */ - public function sql_like($fieldname, $param, $casesensitive = true, $accentsensitive = true, $notlike = false, $escapechar = '\\') { - if (strpos($param, '%') !== false) { - debugging('Potential SQL injection detected, sql_like() expects bound parameters (? or :named)'); - } - - $LIKE = $notlike ? 'NOT LIKE' : 'LIKE'; - - // no accent sensitiveness here for now, sorry - - if ($casesensitive) { - return "$fieldname $LIKE $param ESCAPE '$escapechar'"; - } else { - return "LOWER($fieldname) $LIKE LOWER($param) ESCAPE '$escapechar'"; - } - } - - public function sql_concat(...$arr) { - if (empty($arr)) { - return " ' ' "; - } - foreach ($arr as $k => $v) { - if ($v === "' '") { - $arr[$k] = "'*OCISP*'"; // New mega hack. - } - } - $s = $this->recursive_concat($arr); - return " MOODLELIB.UNDO_MEGA_HACK($s) "; - } - - public function sql_concat_join($separator="' '", $elements = array()) { - if ($separator === "' '") { - $separator = "'*OCISP*'"; // New mega hack. - } - foreach ($elements as $k => $v) { - if ($v === "' '") { - $elements[$k] = "'*OCISP*'"; // New mega hack. - } - } - for ($n = count($elements)-1; $n > 0 ; $n--) { - array_splice($elements, $n, 0, $separator); - } - if (empty($elements)) { - return " ' ' "; - } - $s = $this->recursive_concat($elements); - return " MOODLELIB.UNDO_MEGA_HACK($s) "; - } - - /** - * Return SQL for performing group concatenation on given field/expression - * - * @param string $field - * @param string $separator - * @param string $sort - * @return string - */ - public function sql_group_concat(string $field, string $separator = ', ', string $sort = ''): string { - $fieldsort = $sort ?: '1'; - return "LISTAGG({$field}, '{$separator}') WITHIN GROUP (ORDER BY {$fieldsort})"; - } - - /** - * Returns the SQL text to be used to order by columns, standardising the return - * pattern of null values across database types to sort nulls first when ascending - * and last when descending. - * - * @param string $fieldname The name of the field we need to sort by. - * @param int $sort An order to sort the results in. - * @return string The piece of SQL code to be used in your statement. - */ - public function sql_order_by_null(string $fieldname, int $sort = SORT_ASC): string { - return parent::sql_order_by_null($fieldname, $sort) . ' NULLS ' . ($sort == SORT_ASC ? 'FIRST' : 'LAST'); - } - - /** - * Constructs 'IN()' or '=' sql fragment - * - * Method overriding {@link moodle_database::get_in_or_equal} to be able to get - * more than 1000 elements working, to avoid ORA-01795. We use a pivoting technique - * to be able to transform the params into virtual rows, so the original IN() - * expression gets transformed into a subquery. Once more, be noted that we shouldn't - * be using ever get_in_or_equal() with such number of parameters (proper subquery and/or - * chunking should be used instead). - * - * @param mixed $items A single value or array of values for the expression. - * @param int $type Parameter bounding type : SQL_PARAMS_QM or SQL_PARAMS_NAMED. - * @param string $prefix Named parameter placeholder prefix (a unique counter value is appended to each parameter name). - * @param bool $equal True means we want to equate to the constructed expression, false means we don't want to equate to it. - * @param mixed $onemptyitems This defines the behavior when the array of items provided is empty. Defaults to false, - * meaning throw exceptions. Other values will become part of the returned SQL fragment. - * @throws coding_exception | dml_exception - * @return array A list containing the constructed sql fragment and an array of parameters. - */ - public function get_in_or_equal($items, $type=SQL_PARAMS_QM, $prefix='param', $equal=true, $onemptyitems=false) { - list($sql, $params) = parent::get_in_or_equal($items, $type, $prefix, $equal, $onemptyitems); - - // Less than 1000 elements, nothing to do. - if (count($params) < 1000) { - return array($sql, $params); // Return unmodified. - } - - // Extract the interesting parts of the sql to rewrite. - if (preg_match('!(^.*IN \()([^\)]*)(.*)$!', $sql, $matches) === false) { - return array($sql, $params); // Return unmodified. - } - - $instart = $matches[1]; - $insql = $matches[2]; - $inend = $matches[3]; - $newsql = ''; - - // Some basic verification about the matching going ok. - $insqlarr = explode(',', $insql); - if (count($insqlarr) !== count($params)) { - return array($sql, $params); // Return unmodified. - } - - // Arrived here, we need to chunk and pivot the params, building a new sql (params remain the same). - $addunionclause = false; - while ($chunk = array_splice($insqlarr, 0, 125)) { // Each chunk will handle up to 125 (+125 +1) elements (DECODE max is 255). - $chunksize = count($chunk); - if ($addunionclause) { - $newsql .= "\n UNION ALL"; - } - $newsql .= "\n SELECT DECODE(pivot"; - $counter = 1; - foreach ($chunk as $element) { - $newsql .= ",\n {$counter}, " . trim($element); - $counter++; - } - $newsql .= ")"; - $newsql .= "\n FROM dual"; - $newsql .= "\n CROSS JOIN (SELECT LEVEL AS pivot FROM dual CONNECT BY LEVEL <= {$chunksize})"; - $addunionclause = true; - } - - // Rebuild the complete IN() clause and return it. - return array($instart . $newsql . $inend, $params); - } - - /** - * Mega hacky magic to work around crazy Oracle NULL concats. - * @param array $args - * @return string - */ - protected function recursive_concat(array $args) { - $count = count($args); - if ($count == 1) { - $arg = reset($args); - return $arg; - } - if ($count == 2) { - $args[] = "' '"; - // No return here intentionally. - } - $first = array_shift($args); - $second = array_shift($args); - $third = $this->recursive_concat($args); - return "MOODLELIB.TRICONCAT($first, $second, $third)"; - } - - /** - * Returns the SQL for returning searching one string for the location of another. - */ - public function sql_position($needle, $haystack) { - return "INSTR(($haystack), ($needle))"; - } - - /** - * Returns the SQL to know if one field is empty. - * - * @param string $tablename Name of the table (without prefix). Not used for now but can be - * necessary in the future if we want to use some introspection using - * meta information against the DB. - * @param string $fieldname Name of the field we are going to check - * @param bool $nullablefield For specifying if the field is nullable (true) or no (false) in the DB. - * @param bool $textfield For specifying if it is a text (also called clob) field (true) or a varchar one (false) - * @return string the sql code to be added to check for empty values - */ - public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) { - if ($textfield) { - return " (".$this->sql_compare_text($fieldname)." = ' ') "; - } else { - return " ($fieldname = ' ') "; - } - } - - public function sql_order_by_text($fieldname, $numchars=32) { - return 'dbms_lob.substr(' . $fieldname . ', ' . $numchars . ',1)'; - } - - /** - * Is the required OCI server package installed? - * @return bool - */ - protected function oci_package_installed() { - $sql = "SELECT 1 - FROM user_objects - WHERE object_type = 'PACKAGE BODY' - AND object_name = 'MOODLELIB' - AND status = 'VALID'"; - $this->query_start($sql, null, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - $records = null; - oci_fetch_all($stmt, $records, 0, -1, OCI_FETCHSTATEMENT_BY_ROW); - oci_free_statement($stmt); - return isset($records[0]) && reset($records[0]) ? true : false; - } - - /** - * Try to add required moodle package into oracle server. - */ - protected function attempt_oci_package_install() { - $sqls = file_get_contents(__DIR__.'/oci_native_moodle_package.sql'); - $sqls = preg_split('/^\/$/sm', $sqls); - foreach ($sqls as $sql) { - $sql = trim($sql); - if ($sql === '' or $sql === 'SHOW ERRORS') { - continue; - } - $this->change_database_structure($sql); - } - } - - /** - * Does this driver support tool_replace? - * - * @since Moodle 2.8 - * @return bool - */ - public function replace_all_text_supported() { - return true; - } - - public function session_lock_supported() { - return true; - } - - /** - * Obtain session lock - * @param int $rowid id of the row with session record - * @param int $timeout max allowed time to wait for the lock in seconds - * @return void - */ - public function get_session_lock($rowid, $timeout) { - parent::get_session_lock($rowid, $timeout); - - $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; - $sql = 'SELECT MOODLELIB.GET_LOCK(:lockname, :locktimeout) FROM DUAL'; - $params = array('lockname' => $fullname , 'locktimeout' => $timeout); - $this->query_start($sql, $params, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $this->bind_params($stmt, $params); - $result = oci_execute($stmt, $this->commit_status); - if ($result === false) { // Any failure in get_lock() raises error, causing return of bool false - throw new dml_sessionwait_exception(); - } - $this->query_end($result, $stmt); - oci_free_statement($stmt); - } - - public function release_session_lock($rowid) { - if (!$this->used_for_db_sessions) { - return; - } - - parent::release_session_lock($rowid); - - $fullname = $this->dbname.'-'.$this->prefix.'-session-'.$rowid; - $params = array('lockname' => $fullname); - $sql = 'SELECT MOODLELIB.RELEASE_LOCK(:lockname) FROM DUAL'; - $this->query_start($sql, $params, SQL_QUERY_AUX); - $stmt = $this->parse_query($sql); - $this->bind_params($stmt, $params); - $result = oci_execute($stmt, $this->commit_status); - $this->query_end($result, $stmt); - oci_free_statement($stmt); - } - - /** - * Driver specific start of real database transaction, - * this can not be used directly in code. - * @return void - */ - protected function begin_transaction() { - $this->commit_status = OCI_DEFAULT; //Done! ;-) - } - - /** - * Driver specific commit of real database transaction, - * this can not be used directly in code. - * @return void - */ - protected function commit_transaction() { - $this->query_start('--oracle_commit', NULL, SQL_QUERY_AUX); - $result = oci_commit($this->oci); - $this->commit_status = OCI_COMMIT_ON_SUCCESS; - $this->query_end($result); - } - - /** - * Driver specific abort of real database transaction, - * this can not be used directly in code. - * @return void - */ - protected function rollback_transaction() { - $this->query_start('--oracle_rollback', NULL, SQL_QUERY_AUX); - $result = oci_rollback($this->oci); - $this->commit_status = OCI_COMMIT_ON_SUCCESS; - $this->query_end($result); - } - - /** - * Oracle supports the COUNT() window function and provides a performance improvement. - * - * @return bool - */ - public function is_count_window_function_supported(): bool { - return true; - } -} diff --git a/lib/dml/oci_native_moodle_package.sql b/lib/dml/oci_native_moodle_package.sql deleted file mode 100644 index 1d6dec943bb..00000000000 --- a/lib/dml/oci_native_moodle_package.sql +++ /dev/null @@ -1,151 +0,0 @@ --- 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 . - -/** - * @package core_dml - * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @version 20091010 (plz, keep this updated for easier reference) - */ - -/** - * This sql script generates various PL/SQL packages needed to provide - * cross-db compatibility in the Moodle 2.x DB API with some operations - * not natively supported by Oracle, namely: - * - locking: Application locks used by Moodle DB sessions. It uses - * the DBMS_LOCK package so execution must be granted - * to the Moodle DB user by SYS to work properly. - * - bit ops: To provide cross-db bitwise operations to be used by the - * sql_bitXXX() helper functions - * - one space hacks: One space empty string substitute hacks. - * - * Moodle will not parse this file correctly if it uses Windows line endings. - */ - -CREATE OR REPLACE PACKAGE MOODLELIB AS - -FUNCTION BITOR (value1 IN INTEGER, value2 IN INTEGER) RETURN INTEGER; -FUNCTION BITXOR(value1 IN INTEGER, value2 IN INTEGER) RETURN INTEGER; - -FUNCTION GET_HANDLE (lock_name IN VARCHAR2) RETURN VARCHAR2; -FUNCTION GET_LOCK (lock_name IN VARCHAR2, lock_timeout IN INTEGER) RETURN INTEGER; -FUNCTION RELEASE_LOCK(lock_name IN VARCHAR2) RETURN INTEGER; - -FUNCTION UNDO_DIRTY_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2; -FUNCTION UNDO_MEGA_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2; -FUNCTION TRICONCAT(string1 IN VARCHAR2, string2 IN VARCHAR2, string3 IN VARCHAR2) RETURN VARCHAR2; - -END MOODLELIB; -/ - -CREATE OR REPLACE PACKAGE BODY MOODLELIB AS - -FUNCTION BITOR(value1 IN INTEGER, value2 IN INTEGER) RETURN INTEGER IS - -BEGIN - RETURN value1 + value2 - BITAND(value1,value2); -END BITOR; - -FUNCTION BITXOR(value1 IN INTEGER, value2 IN INTEGER) RETURN INTEGER IS - -BEGIN - RETURN MOODLELIB.BITOR(value1,value2) - BITAND(value1,value2); -END BITXOR; - -FUNCTION GET_HANDLE(lock_name IN VARCHAR2) RETURN VARCHAR2 IS - PRAGMA AUTONOMOUS_TRANSACTION; - lock_handle VARCHAR2(128); - -BEGIN - DBMS_LOCK.ALLOCATE_UNIQUE ( - lockname => lock_name, - lockhandle => lock_handle, - expiration_secs => 864000); - RETURN lock_handle; -END GET_HANDLE; - -FUNCTION GET_LOCK(lock_name IN VARCHAR2, lock_timeout IN INTEGER) RETURN INTEGER IS - lock_status NUMBER; -BEGIN - lock_status := DBMS_LOCK.REQUEST( - lockhandle => GET_HANDLE(lock_name), - lockmode => DBMS_LOCK.X_MODE, -- eXclusive - timeout => lock_timeout, - release_on_commit => FALSE); - CASE lock_status - WHEN 0 THEN NULL; - WHEN 2 THEN RAISE_APPLICATION_ERROR(-20000,'deadlock detected'); - WHEN 4 THEN RAISE_APPLICATION_ERROR(-20000,'lock already obtained'); - ELSE RAISE_APPLICATION_ERROR(-20000,'request lock failed - ' || lock_status); - END CASE; - RETURN 1; -END GET_LOCK; - -FUNCTION RELEASE_LOCK(lock_name IN VARCHAR2) RETURN INTEGER IS - lock_status NUMBER; -BEGIN - lock_status := DBMS_LOCK.RELEASE( - lockhandle => GET_HANDLE(lock_name)); - IF lock_status > 0 THEN - RAISE_APPLICATION_ERROR(-20000,'release lock failed - ' || lock_status); - END IF; - RETURN 1; -END RELEASE_LOCK; - -FUNCTION UNDO_DIRTY_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2 IS - -BEGIN - IF hackedstring = ' ' THEN - RETURN ''; - END IF; - RETURN hackedstring; -END UNDO_DIRTY_HACK; - -FUNCTION UNDO_MEGA_HACK(hackedstring IN VARCHAR2) RETURN VARCHAR2 IS - -BEGIN - IF hackedstring IS NULL THEN - RETURN hackedstring; - END IF; - RETURN REPLACE(hackedstring, '*OCISP*', ' '); -END UNDO_MEGA_HACK; - -FUNCTION TRICONCAT(string1 IN VARCHAR2, string2 IN VARCHAR2, string3 IN VARCHAR2) RETURN VARCHAR2 IS - stringresult VARCHAR2(1333); -BEGIN - IF string1 IS NULL THEN - RETURN NULL; - END IF; - IF string2 IS NULL THEN - RETURN NULL; - END IF; - IF string3 IS NULL THEN - RETURN NULL; - END IF; - - stringresult := CONCAT(CONCAT(MOODLELIB.UNDO_DIRTY_HACK(string1), MOODLELIB.UNDO_DIRTY_HACK(string2)), MOODLELIB.UNDO_DIRTY_HACK(string3)); - - IF stringresult IS NULL THEN - RETURN ' '; - END IF; - - RETURN stringresult; -END; - -END MOODLELIB; -/ - -SHOW ERRORS -/ diff --git a/lib/dml/oci_native_moodle_recordset.php b/lib/dml/oci_native_moodle_recordset.php deleted file mode 100644 index d05464beb2d..00000000000 --- a/lib/dml/oci_native_moodle_recordset.php +++ /dev/null @@ -1,88 +0,0 @@ -. - -/** - * Oracle specific recordset. - * - * @package core_dml - * @copyright 2008 Petr Skoda (http://skodak.org) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__.'/moodle_recordset.php'); - -class oci_native_moodle_recordset extends moodle_recordset { - - protected $stmt; - protected $current; - - public function __construct($stmt) { - $this->stmt = $stmt; - $this->current = $this->fetch_next(); - } - - public function __destruct() { - $this->close(); - } - - private function fetch_next() { - if (!$this->stmt) { - return false; - } - if (!$row = oci_fetch_array($this->stmt, OCI_ASSOC + OCI_RETURN_NULLS + OCI_RETURN_LOBS)) { - oci_free_statement($this->stmt); - $this->stmt = null; - return false; - } - - $row = array_change_key_case($row, CASE_LOWER); - unset($row['oracle_rownum']); - array_walk($row, array('oci_native_moodle_database', 'onespace2empty')); - return $row; - } - - public function current(): stdClass { - return (object)$this->current; - } - - #[\ReturnTypeWillChange] - public function key() { - // return first column value as key - if (!$this->current) { - return false; - } - $key = reset($this->current); - return $key; - } - - public function next(): void { - $this->current = $this->fetch_next(); - } - - public function valid(): bool { - return !empty($this->current); - } - - public function close() { - if ($this->stmt) { - oci_free_statement($this->stmt); - $this->stmt = null; - } - $this->current = null; - } -} diff --git a/lib/dml/oci_native_moodle_temptables.php b/lib/dml/oci_native_moodle_temptables.php deleted file mode 100644 index 3b7fd3f7dd5..00000000000 --- a/lib/dml/oci_native_moodle_temptables.php +++ /dev/null @@ -1,70 +0,0 @@ -. - -/** - * OCI specific temptables store. Needed because temporary tables - * in Oracle are global (to all sessions), so we need to rename them - * on the fly in order to get local (different for each session) table names. - * Also used to be able to retrieve temp table names included in the get_tables() - * method of the DB. - * - * @package core_dml - * @copyright 2009 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once(__DIR__.'/moodle_temptables.php'); - -class oci_native_moodle_temptables extends moodle_temptables { - - /** @var int To store unique_session_id. Needed for temp tables unique naming (upto 24cc) */ - protected $unique_session_id; // - /** @var int To get incrementally different temptable names on each add_temptable() request */ - protected $counter; - - /** - * Creates new moodle_temptables instance - * @param object moodle_database instance - */ - public function __construct($mdb, $unique_session_id) { - $this->unique_session_id = $unique_session_id; - $this->counter = 1; - parent::__construct($mdb); - } - - /** - * Add one temptable to the store. - * - * Overridden because OCI only support global temptables, so we need to change completely the name, based - * in unique session identifier, to get local-like temp tables support - * tables before the prefix. - * - * Given one moodle temptable name (without prefix), add it to the store, with the - * key being the original moodle name and the value being the real db temptable name - * already prefixed - * - * Override and use this *only* if the database requires modification in the table name. - * - * @param string $tablename name without prefix of the table created as temptable - */ - public function add_temptable($tablename) { - // TODO: throw exception if exists: if ($this->is_temptable... - $this->temptables[$tablename] = $this->prefix . $this->unique_session_id . $this->counter; - $this->counter++; - } -} diff --git a/lib/dml/pgsql_native_moodle_database.php b/lib/dml/pgsql_native_moodle_database.php index 5b9f2f164d9..13844654d96 100644 --- a/lib/dml/pgsql_native_moodle_database.php +++ b/lib/dml/pgsql_native_moodle_database.php @@ -87,7 +87,7 @@ class pgsql_native_moodle_database extends moodle_database { /** * Returns database family type - describes SQL dialect * Note: can be used before connect() - * @return string db family name (mysql, postgres, mssql, oracle, etc.) + * @return string db family name (mysql, postgres, mssql, etc.) */ public function get_dbfamily() { return 'postgres'; @@ -96,7 +96,7 @@ class pgsql_native_moodle_database extends moodle_database { /** * Returns more specific database driver type * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string db type mysqli, pgsql, mssql, sqlsrv */ protected function get_dbtype() { return 'pgsql'; diff --git a/lib/dml/sqlite3_pdo_moodle_database.php b/lib/dml/sqlite3_pdo_moodle_database.php index 394e257bf2c..efd221611cd 100644 --- a/lib/dml/sqlite3_pdo_moodle_database.php +++ b/lib/dml/sqlite3_pdo_moodle_database.php @@ -50,7 +50,7 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database { /** * Returns database family type - describes SQL dialect * Note: can be used before connect() - * @return string db family name (mysql, postgres, mssql, oracle, etc.) + * @return string db family name (mysql, postgres, mssql, etc.) */ public function get_dbfamily() { return 'sqlite'; @@ -59,7 +59,7 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database { /** * Returns more specific database driver type * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string db type mysqli, pgsql, mssql, sqlsrv */ protected function get_dbtype() { return 'sqlite3'; diff --git a/lib/dml/sqlsrv_native_moodle_database.php b/lib/dml/sqlsrv_native_moodle_database.php index 94443287b09..121b1948c1a 100644 --- a/lib/dml/sqlsrv_native_moodle_database.php +++ b/lib/dml/sqlsrv_native_moodle_database.php @@ -102,7 +102,7 @@ class sqlsrv_native_moodle_database extends moodle_database { /** * Returns database family type - describes SQL dialect * Note: can be used before connect() - * @return string db family name (mysql, postgres, mssql, sqlsrv, oracle, etc.) + * @return string db family name (mysql, postgres, mssql, sqlsrv, etc.) */ public function get_dbfamily() { return 'mssql'; @@ -111,7 +111,7 @@ class sqlsrv_native_moodle_database extends moodle_database { /** * Returns more specific database driver type * Note: can be used before connect() - * @return string db type mysqli, pgsql, oci, mssql, sqlsrv + * @return string db type mysqli, pgsql, mssql, sqlsrv */ protected function get_dbtype() { return 'sqlsrv'; diff --git a/lib/dml/tests/dml_test.php b/lib/dml/tests/dml_test.php index 525ea2c7369..d3f367817f0 100644 --- a/lib/dml/tests/dml_test.php +++ b/lib/dml/tests/dml_test.php @@ -573,150 +573,6 @@ EOD; $this->assertSame(strtok('?'), 'b'); } - public function test_tweak_param_names(): void { - - // Note the tweak_param_names() method is only available in the oracle driver, - // hence we look for expected results indirectly, by testing various DML methods. - // with some "extreme" conditions causing the tweak to happen. - $DB = $this->tdb; - $dbman = $this->tdb->get_manager(); - - $table = $this->get_test_table(); - $tablename = $table->getName(); - - // Prepare some long column names. - $intnearmax = str_pad('long_int_columnname_near_', \xmldb_field::NAME_MAX_LENGTH - 1, 'x'); - $decnearmax = str_pad('long_dec_columnname_near_', \xmldb_field::NAME_MAX_LENGTH - 1, 'x'); - $strnearmax = str_pad('long_str_columnname_near_', \xmldb_field::NAME_MAX_LENGTH - 1, 'x'); - $intmax = str_pad('long_int_columnname_max', \xmldb_field::NAME_MAX_LENGTH, 'x'); - $decmax = str_pad('long_dec_columnname_max', \xmldb_field::NAME_MAX_LENGTH, 'x'); - $strmax = str_pad('long_str_columnname_max', \xmldb_field::NAME_MAX_LENGTH, 'x'); - - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - // Add some correct columns with \xmldb_field::NAME_MAX_LENGTH minus 1 chars in the name. - $table->add_field($intnearmax, XMLDB_TYPE_INTEGER, '10'); - $table->add_field($decnearmax, XMLDB_TYPE_NUMBER, '10,2'); - $table->add_field($strnearmax, XMLDB_TYPE_CHAR, '100'); - // Add some correct columns with xmldb_table::NAME_MAX_LENGTH chars in the name. - $table->add_field($intmax, XMLDB_TYPE_INTEGER, '10'); - $table->add_field($decmax, XMLDB_TYPE_NUMBER, '10,2'); - $table->add_field($strmax, XMLDB_TYPE_CHAR, '100'); - - $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); - - $dbman->create_table($table); - - $this->assertTrue($dbman->table_exists($tablename)); - - // Test insert record. - $rec1 = new \stdClass(); - $rec1->{$intnearmax} = 62; - $rec1->{$decnearmax} = 62.62; - $rec1->{$strnearmax} = '62'; - $rec1->{$intmax} = 63; - $rec1->{$decmax} = 63.63; - $rec1->{$strmax} = '63'; - - // Insert_record(). - $rec1->id = $DB->insert_record($tablename, $rec1); - $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id))); - - // Update_record(). - $DB->update_record($tablename, $rec1); - $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id))); - - // Set_field(). - $rec1->{$intnearmax} = 620; - $DB->set_field($tablename, $intnearmax, $rec1->{$intnearmax}, - array('id' => $rec1->id, $intnearmax => 62)); - $rec1->{$decnearmax} = 620.62; - $DB->set_field($tablename, $decnearmax, $rec1->{$decnearmax}, - array('id' => $rec1->id, $decnearmax => 62.62)); - $rec1->{$strnearmax} = '620'; - $DB->set_field($tablename, $strnearmax, $rec1->{$strnearmax}, - array('id' => $rec1->id, $strnearmax => '62')); - $rec1->{$intmax} = 630; - $DB->set_field($tablename, $intmax, $rec1->{$intmax}, - array('id' => $rec1->id, $intmax => 63)); - $rec1->{$decmax} = 630.63; - $DB->set_field($tablename, $decmax, $rec1->{$decmax}, - array('id' => $rec1->id, $decmax => 63.63)); - $rec1->{$strmax} = '630'; - $DB->set_field($tablename, $strmax, $rec1->{$strmax}, - array('id' => $rec1->id, $strmax => '63')); - $this->assertEquals($rec1, $DB->get_record($tablename, array('id' => $rec1->id))); - - // Delete_records(). - $rec2 = $DB->get_record($tablename, array('id' => $rec1->id)); - $rec2->id = $DB->insert_record($tablename, $rec2); - $this->assertEquals(2, $DB->count_records($tablename)); - $DB->delete_records($tablename, (array) $rec2); - $this->assertEquals(1, $DB->count_records($tablename)); - - // Get_recordset(). - $rs = $DB->get_recordset($tablename, (array) $rec1); - $iterations = 0; - foreach ($rs as $rec2) { - $iterations++; - } - $rs->close(); - $this->assertEquals(1, $iterations); - $this->assertEquals($rec1, $rec2); - - // Get_records(). - $recs = $DB->get_records($tablename, (array) $rec1); - $this->assertCount(1, $recs); - $this->assertEquals($rec1, reset($recs)); - - // Get_fieldset_select(). - $select = "id = :id AND - $intnearmax = :$intnearmax AND - $decnearmax = :$decnearmax AND - $strnearmax = :$strnearmax AND - $intmax = :$intmax AND - $decmax = :$decmax AND - $strmax = :$strmax"; - $fields = $DB->get_fieldset_select($tablename, $intnearmax, $select, (array)$rec1); - $this->assertCount(1, $fields); - $this->assertEquals($rec1->{$intnearmax}, reset($fields)); - $fields = $DB->get_fieldset_select($tablename, $decnearmax, $select, (array)$rec1); - $this->assertEquals($rec1->{$decnearmax}, reset($fields)); - $fields = $DB->get_fieldset_select($tablename, $strnearmax, $select, (array)$rec1); - $this->assertEquals($rec1->{$strnearmax}, reset($fields)); - $fields = $DB->get_fieldset_select($tablename, $intmax, $select, (array)$rec1); - $this->assertEquals($rec1->{$intmax}, reset($fields)); - $fields = $DB->get_fieldset_select($tablename, $decmax, $select, (array)$rec1); - $this->assertEquals($rec1->{$decmax}, reset($fields)); - $fields = $DB->get_fieldset_select($tablename, $strmax, $select, (array)$rec1); - $this->assertEquals($rec1->{$strmax}, reset($fields)); - - // Overlapping placeholders (progressive str_replace). - $nearmaxparam = str_pad('allowed_long_param', \xmldb_field::NAME_MAX_LENGTH - 1, 'x'); - $maxparam = str_pad('allowed_long_param', \xmldb_field::NAME_MAX_LENGTH, 'x'); - $overlapselect = "id = :p AND - $intnearmax = :param1 AND - $decnearmax = :param2 AND - $strnearmax = :{$nearmaxparam} AND - $intmax = :{$maxparam} AND - $decmax = :param_ AND - $strmax = :param__"; - $overlapparams = array( - 'p' => $rec1->id, - 'param1' => $rec1->{$intnearmax}, - 'param2' => $rec1->{$decnearmax}, - $nearmaxparam => $rec1->{$strnearmax}, - $maxparam => $rec1->{$intmax}, - 'param_' => $rec1->{$decmax}, - 'param__' => $rec1->{$strmax}); - $recs = $DB->get_records_select($tablename, $overlapselect, $overlapparams); - $this->assertCount(1, $recs); - $this->assertEquals($rec1, reset($recs)); - - // Execute(). - $DB->execute("DELETE FROM {{$tablename}} WHERE $select", (array)$rec1); - $this->assertEquals(0, $DB->count_records($tablename)); - } - public function test_get_tables(): void { $DB = $this->tdb; $dbman = $this->tdb->get_manager(); @@ -4084,8 +3940,8 @@ EOD; $DB->insert_record($tablename, array('name'=>'aaaa', 'description'=>'aaaacccccccccccccccccc')); $DB->insert_record($tablename, array('name'=>'xxxx', 'description'=>'123456789a123456789b123456789c123456789d')); - // Only some supported databases truncate TEXT fields for comparisons, currently MSSQL and Oracle. - $dbtruncatestextfields = ($DB->get_dbfamily() == 'mssql' || $DB->get_dbfamily() == 'oracle'); + // Only some supported databases truncate TEXT fields for comparisons, currently MSSQL. + $dbtruncatestextfields = ($DB->get_dbfamily() == 'mssql'); if ($dbtruncatestextfields) { // Ensure truncation behaves as expected. @@ -4162,7 +4018,7 @@ EOD; if ($family === 'mysql' or $family === 'mssql') { $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages. This is usually caused by accent insensitive default collation."); } else { - // This should not happen, PostgreSQL and Oracle do not support accent insensitive uniqueness. + // This should not happen, PostgreSQL does not support accent insensitive uniqueness. $this->fail("Unique index is accent insensitive, this may cause problems for non-ascii languages."); } throw($e); @@ -4414,7 +4270,8 @@ EOD; $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); $dbman->create_table($table); - // Regarding 1300 length - all drivers except Oracle support larger values (2K+), but this hits a limit on Oracle. + // Regarding the 1300 length - all supported drivers allow larger values (2K+), + // previously limited by Oracle, which no longer applies as Oracle support has been removed. $DB->insert_record($tablename, [ 'charshort' => 'áéíóú', 'charlong' => str_repeat('A', 512), @@ -5070,9 +4927,9 @@ EOD; $DB->insert_record($tablename, array('course' => 2, 'content' => 'universe', 'name'=>'abc')); // Test grouping by expressions in the query. MDL-26819. Note that there are 4 ways: - // - By column position (GROUP by 1) - Not supported by mssql & oracle + // - By column position (GROUP by 1) - Not supported by mssql // - By column name (GROUP by course) - Supported by all, but leading to wrong results - // - By column alias (GROUP by casecol) - Not supported by mssql & oracle + // - By column alias (GROUP by casecol) - Not supported by mssql // - By complete expression (GROUP BY CASE ...) - 100% cross-db, this test checks it $sql = "SELECT (CASE WHEN course = 3 THEN 1 ELSE 0 END) AS casecol, COUNT(1) AS countrecs, diff --git a/lib/dmllib.php b/lib/dmllib.php index 4c57a8d698a..e3415861514 100644 --- a/lib/dmllib.php +++ b/lib/dmllib.php @@ -25,7 +25,7 @@ use core\router\response\not_found_response; * This library contains all the Data Manipulation Language (DML) functions * used to interact with the DB. All the dunctions in this library must be * generic and work against the major number of RDBMS possible. This is the - * list of currently supported and tested DBs: mysql, postresql, mssql, oracle + * list of currently supported and tested DBs: mysql, postresql, and mssql. * * This library is automatically included by Moodle core so you never need to * include it yourself. @@ -322,10 +322,6 @@ function setup_DB() { $CFG->dbtype = 'pgsql'; break; - case 'oci8po': - $CFG->dbtype = 'oci'; - break; - case 'mysql' : $CFG->dbtype = 'mysqli'; break; diff --git a/lib/enrollib.php b/lib/enrollib.php index 2d03409a37e..0dbaa1b07c9 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -809,7 +809,7 @@ function enrol_get_my_courses($fields = null, $sort = null, $limit = 0, $coursei } } - // Note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why + // Note: we can not use DISTINCT + text fields due to MS limitations, that is why // we have the subselect there. $sql = "SELECT $coursefields $ccselect $timeaccessselect FROM {course} c @@ -1124,7 +1124,7 @@ function enrol_get_all_users_courses($userid, $onlyactive = false, $fields = nul $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; $params['contextlevel'] = CONTEXT_COURSE; - //note: we can not use DISTINCT + text fields due to Oracle and MS limitations, that is why we have the subselect there + //note: we can not use DISTINCT + text fields due to MS limitations, that is why we have the subselect there $sql = "SELECT $coursefields $ccselect FROM {course} c JOIN (SELECT DISTINCT e.courseid diff --git a/lib/phpunit/classes/database_driver_testcase.php b/lib/phpunit/classes/database_driver_testcase.php index 9c2ec342e96..06e1c83eedb 100644 --- a/lib/phpunit/classes/database_driver_testcase.php +++ b/lib/phpunit/classes/database_driver_testcase.php @@ -36,7 +36,6 @@ * 1=>array('dbtype'=>'mysqli', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'root', 'dbpass'=>'', 'prefix'=>'phpu2_'), * 2=>array('dbtype'=>'pgsql', 'dbhost'=>'localhost', 'dbname'=>'moodle', 'dbuser'=>'postgres', 'dbpass'=>'', 'prefix'=>'phpu2_'), * 3=>array('dbtype'=>'sqlsrv', 'dbhost'=>'127.0.0.1', 'dbname'=>'moodle', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'phpu2_'), - * 4=>array('dbtype'=>'oci', 'dbhost'=>'127.0.0.1', 'dbname'=>'XE', 'dbuser'=>'sa', 'dbpass'=>'', 'prefix'=>'t_'), * ); * define('PHPUNIT_TEST_DRIVER')=1; //number is index in the previous array * diff --git a/lib/searchlib.php b/lib/searchlib.php index c2caf7ae79f..6845610cc81 100644 --- a/lib/searchlib.php +++ b/lib/searchlib.php @@ -422,7 +422,7 @@ function search_generate_SQL($parsetree, $datafield, $metafield, $mainidfield, $ $type = $parsetree[$i]->getType(); $value = $parsetree[$i]->getValue(); - /// Under Oracle and MSSQL, transform TOKEN searches into STRING searches and trim +- chars + // Under MSSQL, transform TOKEN searches into STRING searches and trim +- chars. if (!$DB->sql_regex_supported()) { $value = trim($value, '+-'); if ($type == TOKEN_EXACT) { diff --git a/lib/table/classes/flexible_table.php b/lib/table/classes/flexible_table.php index 9d52cad3373..7546419fb5b 100644 --- a/lib/table/classes/flexible_table.php +++ b/lib/table/classes/flexible_table.php @@ -274,7 +274,7 @@ class flexible_table { } /** - * Use text sorting functions for this column (required for text columns with Oracle). + * Use text sorting functions for this column. * Be warned that you cannot use this with column aliases. You can only do this * with real columns. See MDL-40481 for an example. * @param string column name diff --git a/lib/testing/classes/util.php b/lib/testing/classes/util.php index 85360550ca0..3073cde3bf7 100644 --- a/lib/testing/classes/util.php +++ b/lib/testing/classes/util.php @@ -393,20 +393,6 @@ abstract class testing_util { } $rs->close(); return $empties; - } else if ($dbfamily === 'oracle') { - $sequences = self::get_sequencenames(); - $sequences = array_map('strtoupper', $sequences); - $lookup = array_flip($sequences); - $empties = []; - [$seqs, $params] = $DB->get_in_or_equal($sequences); - $sql = "SELECT sequence_name FROM user_sequences WHERE last_number = 1 AND sequence_name $seqs"; - $rs = $DB->get_recordset_sql($sql, $params); - foreach ($rs as $seq) { - $table = $lookup[$seq->sequence_name]; - $empties[$table] = $table; - } - $rs->close(); - return $empties; } else { return []; } @@ -528,48 +514,6 @@ abstract class testing_util { if ($queries) { $DB->change_database_structure(implode(';', $queries)); } - } else if ($dbfamily === 'oracle') { - $sequences = self::get_sequencenames(); - $sequences = array_map('strtoupper', $sequences); - $lookup = array_flip($sequences); - - $current = []; - [$seqs, $params] = $DB->get_in_or_equal($sequences); - $sql = "SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name $seqs"; - $rs = $DB->get_recordset_sql($sql, $params); - foreach ($rs as $seq) { - $table = $lookup[$seq->sequence_name]; - $current[$table] = $seq->last_number; - } - $rs->close(); - - foreach ($data as $table => $records) { - // If table is not modified then no need to do anything. - if (!isset($updatedtables[$table])) { - continue; - } - if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) { - $lastrecord = end($records); - if ($lastrecord) { - $nextid = $lastrecord->id + 1; - } else { - $nextid = 1; - } - if (!isset($current[$table])) { - $DB->get_manager()->reset_sequence($table); - } else if ($nextid == $current[$table]) { - continue; - } - // Reset as fast as possible. - // Alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle. - $seqname = $sequences[$table]; - $cachesize = $DB->get_manager()->generator->sequence_cache_size; - $DB->change_database_structure("DROP SEQUENCE $seqname"); - $DB->change_database_structure( - "CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize", - ); - } - } } else { // Note: does mssql support any kind of faster reset? // This also implies mssql will not use unique sequence values. diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 6dcf250cd2b..eee98d7c38f 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -2137,7 +2137,7 @@ EOF; } /** - * Checks if database family used is using one of the specified, else skip. (mysql, postgres, mssql, oracle, etc.) + * Checks if database family used is using one of the specified, else skip. (mysql, postgres, mssql, etc.) * * @Given /^database family used is one of the following:$/ * @param TableNode $databasefamilies list of database. diff --git a/lib/tests/environment_test.php b/lib/tests/environment_test.php index 0d65180e7c6..beeb56e65d8 100644 --- a/lib/tests/environment_test.php +++ b/lib/tests/environment_test.php @@ -92,10 +92,6 @@ final class environment_test extends \advanced_testcase { // If we're on a 32-bit system, skip 64-bit check. 32-bit PHP has PHP_INT_SIZE set to 4. $this->markTestSkipped('64-bit check is not necessary for unit testing.'); } - if ($result->info === 'oracle_database_usage') { - // If we're on a system that uses the Oracle database, skip the Oracle check. - $this->markTestSkipped('Oracle database check is not necessary for unit testing.'); - } } $info = "{$result->part}:{$result->info}"; $this->assertTrue($result->getStatus(), "Problem detected in environment ($info), fix all warnings and errors!"); diff --git a/lib/tests/upgradelib_test.php b/lib/tests/upgradelib_test.php index ac02477adcd..187c793e8b9 100644 --- a/lib/tests/upgradelib_test.php +++ b/lib/tests/upgradelib_test.php @@ -1390,38 +1390,6 @@ final class upgradelib_test extends advanced_testcase { } } - /** - * Test the check_oracle_usage check when the Moodle instance is not using Oracle as a database architecture. - * - * @covers ::check_oracle_usage - */ - public function test_check_oracle_usage_is_not_used(): void { - global $CFG; - - $this->resetAfterTest(); - $CFG->dbtype = 'pgsql'; - - $result = new environment_results('custom_checks'); - $this->assertNull(check_oracle_usage($result)); - } - - /** - * Test the check_oracle_usage check when the Moodle instance is using Oracle as a database architecture. - * - * @covers ::check_oracle_usage - */ - public function test_check_oracle_usage_is_used(): void { - global $CFG; - - $this->resetAfterTest(); - $CFG->dbtype = 'oci'; - - $result = new environment_results('custom_checks'); - $this->assertInstanceOf(environment_results::class, check_oracle_usage($result)); - $this->assertEquals('oracle_database_usage', $result->getInfo()); - $this->assertFalse($result->getStatus()); - } - /** * Data provider of usermenu items. * diff --git a/lib/upgradelib.php b/lib/upgradelib.php index a6728d06681..b05ec774231 100644 --- a/lib/upgradelib.php +++ b/lib/upgradelib.php @@ -2831,29 +2831,6 @@ function check_mod_assignment(environment_results $result): ?environment_results return null; } -/** - * Check whether the Oracle database is currently being used and warn if so. - * - * The Oracle database support will be removed in a future version (4.5) as it is no longer supported by PHP. - * - * @param environment_results $result object to update, if relevant - * @return environment_results|null updated results or null if the current database is not Oracle. - * - * @see https://tracker.moodle.org/browse/MDL-80166 for further information. - */ -function check_oracle_usage(environment_results $result): ?environment_results { - global $CFG; - - // Checking database type. - if ($CFG->dbtype === 'oci') { - $result->setInfo('oracle_database_usage'); - $result->setFeedbackStr('oracledatabaseinuse'); - return $result; - } - - return null; -} - /** * Check if asynchronous backups are enabled. * diff --git a/lib/xmldb/xmldb_field.php b/lib/xmldb/xmldb_field.php index b051eb4ca9d..16748712ff2 100644 --- a/lib/xmldb/xmldb_field.php +++ b/lib/xmldb/xmldb_field.php @@ -48,7 +48,6 @@ class xmldb_field extends xmldb_object { /** * Note: - * - Oracle: VARCHAR2 has a limit of 4000 bytes * - SQL Server: NVARCHAR has a limit of 40000 chars * - MySQL: VARCHAR 65,535 chars * - PostgreSQL: no limit diff --git a/mod/feedback/classes/privacy/provider.php b/mod/feedback/classes/privacy/provider.php index 43baeb9d6a4..dfb7da06c9d 100644 --- a/mod/feedback/classes/privacy/provider.php +++ b/mod/feedback/classes/privacy/provider.php @@ -454,6 +454,7 @@ class provider implements // Oracle does not support UNION on text fields, therefore we must get the itemdescription // and valuevalue after doing the union by joining on the result. + // TODO: Optimise the query, as Oracle-specific constraints no longer apply. $sql = " SELECT q.*, diff --git a/mod/forum/lib.php b/mod/forum/lib.php index f89027b69f5..e86090c0be7 100644 --- a/mod/forum/lib.php +++ b/mod/forum/lib.php @@ -5929,7 +5929,7 @@ function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, // Prepare SQL to both count and search. // We alias user.id to useridx because we forum_posts already has a userid field and not aliasing this would break - // oracle and mssql. + // mssql. $userfieldsapi = \core_user\fields::for_userpic(); $userfields = $userfieldsapi->get_sql('u', false, '', 'useridx', false)->selects; $countsql = 'SELECT COUNT(*) '; diff --git a/mod/glossary/lib.php b/mod/glossary/lib.php index fab83aff161..0e4756ffd4c 100644 --- a/mod/glossary/lib.php +++ b/mod/glossary/lib.php @@ -1494,11 +1494,12 @@ function glossary_search($course, $searchterms, $extended = 0, $glossary = NULL) foreach ($searchterms as $searchterm) { $i++; - $NOT = false; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle - /// will use it to simulate the "-" operator with LIKE clause + // Initially we aren't going to perform NOT LIKE searches, only MSSQL + // will use it to simulate the "-" operator with LIKE clause. + $NOT = false; - /// Under Oracle and MSSQL, trim the + and - operators and perform - /// simpler LIKE (or NOT LIKE) queries + // Under MSSQL, trim the + and - operators and perform + // simpler LIKE (or NOT LIKE) queries if (!$DB->sql_regex_supported()) { if (substr($searchterm, 0, 1) == '-') { $NOT = true; @@ -3815,7 +3816,7 @@ function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossa foreach ($terms as $searchterm) { $i++; - $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle + $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL // will use it to simulate the "-" operator with LIKE clause. if (empty($fullsearch)) { @@ -3827,7 +3828,7 @@ function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossa } $params['emptychar' . $i] = ''; - // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries. + // Under MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries. if (!$DB->sql_regex_supported()) { if (substr($searchterm, 0, 1) === '-') { $not = true; diff --git a/mod/quiz/accessrule/seb/tests/privacy/provider_test.php b/mod/quiz/accessrule/seb/tests/privacy/provider_test.php index 09f79621da3..a8b272cef4b 100644 --- a/mod/quiz/accessrule/seb/tests/privacy/provider_test.php +++ b/mod/quiz/accessrule/seb/tests/privacy/provider_test.php @@ -108,7 +108,7 @@ final class provider_test extends provider_testcase { // (note this is only effective with databases not using fed (+1000) sequences // per table, like postgres and mysql do, rendering this useless. In any // case better to have the situation covered by some DBs, - // like sqlsrv or oracle than by none). + // like sqlsrv than by none). $this->getDataGenerator()->create_module('label', ['course' => $this->course->id]); $contextlist = provider::get_contexts_for_userid($this->user->id); diff --git a/mod/quiz/classes/privacy/provider.php b/mod/quiz/classes/privacy/provider.php index 8d77a18f684..6b397fabaec 100644 --- a/mod/quiz/classes/privacy/provider.php +++ b/mod/quiz/classes/privacy/provider.php @@ -497,8 +497,7 @@ class provider implements $userid ); - // The layout column causes the union in the following query to fail on Oracle, it also appears to not be used. - // So we can filter the return values to be only those used to generate the data, this will have the benefit + // Filtering the return values to be only those used to generate the data, this will have the benefit // improving performance on all databases as we will no longer be returning a text field for each row. $attemptfields = 'qa.id, qa.quiz, qa.userid, qa.attempt, qa.uniqueid, qa.preview, qa.state, qa.timestart, ' . 'qa.timefinish, qa.timemodified, qa.timemodifiedoffline, qa.timecheckstate, qa.sumgrades, ' . diff --git a/mod/quiz/classes/question/bank/qbank_helper.php b/mod/quiz/classes/question/bank/qbank_helper.php index 553e03cd749..fbfc98c653f 100644 --- a/mod/quiz/classes/question/bank/qbank_helper.php +++ b/mod/quiz/classes/question/bank/qbank_helper.php @@ -129,6 +129,7 @@ class qbank_helper { -- version we could consider digging the old code out of git history from -- just before the commit that added this comment. -- For relevant question_bank_entries, this gets the latest non-draft slot number. + -- TODO: Optimise the query, as Oracle-specific constraints no longer apply. LEFT JOIN ( SELECT lv.questionbankentryid, MAX(CASE WHEN lv.status <> :draft THEN lv.version END) AS usableversion, diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 91f36ef5102..22b4f3c2090 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -800,7 +800,6 @@ function quiz_update_open_attempts(array $conditions) { * Each database handles updates with inner joins differently: * - mysql does not allow a FROM clause * - postgres and mssql allow FROM but handle table aliases differently - * - oracle requires a subquery * * Different code for each database. */ @@ -827,7 +826,7 @@ function quiz_update_open_attempts(array $conditions) { JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id WHERE $attemptselect"; } else { - // oracle, sqlite and others + // Sqlite and others. $updatesql = "UPDATE {quiz_attempts} quiza SET timecheckstate = ( SELECT $timecheckstatesql diff --git a/mod/scorm/tests/behat/scorm_activity_completion.feature b/mod/scorm/tests/behat/scorm_activity_completion.feature index 6536166804e..c460f0a478e 100644 --- a/mod/scorm/tests/behat/scorm_activity_completion.feature +++ b/mod/scorm/tests/behat/scorm_activity_completion.feature @@ -102,7 +102,7 @@ Feature: View activity completion in the SCORM activity And I wait until "Score: 33" "text" exists And I switch to the main frame # We need to get some time till the last item is marked as done (or it won't be ready in slow databases). - # This could be a pause of a few seconds (working ok in super-slow oracle docker database), but re-visiting + # This could be a pause of a few seconds, but re-visiting # any of the pages seems to be doing the work too under that very same slow environment. And I click on "Par?" "list_item" And I switch to "scorm_object" iframe diff --git a/question/format/xml/tests/qformat_xml_import_export_test.php b/question/format/xml/tests/qformat_xml_import_export_test.php index 92fa6d77e16..b38935bc9e4 100644 --- a/question/format/xml/tests/qformat_xml_import_export_test.php +++ b/question/format/xml/tests/qformat_xml_import_export_test.php @@ -92,7 +92,7 @@ final class qformat_xml_import_export_test extends advanced_testcase { $xml = preg_replace('~(?<=)~', '0', $xml); // Deal with how different databases output numbers. Only match when only thing in a tag. - $xml = preg_replace("~>.0000000<~", '>0<', $xml); // How Oracle outputs 0.0000000. + $xml = preg_replace("~>.0000000<~", '>0<', $xml); // Needed by MS SQL Server database. $xml = preg_replace("~(\.(:?[0-9]*[1-9])?)0*<~", '$1<', $xml); // Other cases of trailing 0s $xml = preg_replace("~([0-9]).<~", '$1<', $xml); // Stray . in 1. after last step. diff --git a/tag/classes/area.php b/tag/classes/area.php index 9e79fac970f..8927c010c4a 100644 --- a/tag/classes/area.php +++ b/tag/classes/area.php @@ -421,6 +421,7 @@ class core_tag_area { // Find all tags that are related to the tags being moved and make sure they are present in the target tagcoll. // This query is a little complicated because Oracle does not allow to run SELECT DISTINCT on CLOB fields. + // TODO: Optimise the query, as Oracle-specific constraints no longer apply. $sql = "SELECT name, rawname, description, descriptionformat, userid, isstandard, flag, timemodified ". "FROM {tag} WHERE id IN ". "(SELECT r.id ". @@ -455,6 +456,7 @@ class core_tag_area { // Find all tags that are used for this itemtype/component and are not present in the target tag collection. // This query is a little complicated because Oracle does not allow to run SELECT DISTINCT on CLOB fields. + // TODO: Optimise the query, as Oracle-specific constraints no longer apply. $sql = "SELECT id, name, rawname, description, descriptionformat, userid, isstandard, flag, timemodified FROM {tag} WHERE id IN (SELECT t.id diff --git a/user/classes/fields.php b/user/classes/fields.php index c4154d83308..76660c764d1 100644 --- a/user/classes/fields.php +++ b/user/classes/fields.php @@ -536,7 +536,7 @@ class fields { $DB->sql_equal($fieldalias . '.shortname', $placeholder, false) . " LEFT JOIN {user_info_data} $dataalias ON $dataalias.fieldid = $fieldalias.id AND $dataalias.userid = {$usertable}id"; - // For Oracle we need to convert the field into a usable format. + // For sqlsrv we need to convert the field into a usable format. $fieldsql = $DB->sql_compare_text($dataalias . '.data', 255); $selects .= ", $fieldsql AS $prefix$field"; $mappings[$field] = $fieldsql; @@ -613,7 +613,7 @@ class fields { } if (core_text::strlen($chunk) > 0) { - // If content is just whitespace, add to elements directly (also Oracle doesn't support passing ' ' as param). + // If content is just whitespace, add it directly to elements to handle it appropriately. if (preg_match('/^\s+$/', $chunk)) { $elements[] = "'$chunk'"; } else { diff --git a/user/classes/table/participants_search.php b/user/classes/table/participants_search.php index cd1b7b6f835..c82117b0d2a 100644 --- a/user/classes/table/participants_search.php +++ b/user/classes/table/participants_search.php @@ -330,8 +330,8 @@ class participants_search { $wheresjoin = ' AND NOT '; // Some of the $where conditions may begin with `NOT` which results in `AND NOT NOT ...`. - // To prevent this from breaking on Oracle the inner WHERE clause is wrapped in brackets, making it - // `AND NOT (NOT ...)` which is valid in all DBs. + // To ensure consistent SQL syntax across databases, the inner WHERE clause is wrapped in brackets, + // making it `AND NOT (NOT ...)`, which is valid and improves readability. $wheres = array_map(function($where) { return "({$where})"; }, $wheres);