libdir . '/xmldb/classes/XMLDBConstants.php'); /// Add main XMLDB Generator require_once($CFG->libdir . '/xmldb/classes/generators/XMLDBGenerator.class.php'); /// Add required XMLDB DB classes require_once($CFG->libdir . '/xmldb/classes/XMLDBObject.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBFile.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBStructure.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBTable.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBField.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBKey.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBIndex.class.php'); require_once($CFG->libdir . '/xmldb/classes/XMLDBStatement.class.php'); /// Based on $CFG->dbtype, add the proper generator class require_once($CFG->libdir . '/xmldb/classes/generators/' . $CFG->dbtype . '/' . $CFG->dbtype . '.class.php'); /// Add other libraries require_once($CFG->libdir . '/xmlize.php'); /** * Add a new field to a table, or modify an existing one (if oldfield is defined). * Warning: Please be careful on primary keys, as this function will eat auto_increments * * @uses $CFG * @uses $db * @param string $table the name of the table to modify. (Without the prefix.) * @param string $oldfield If changing an existing column, the name of that column. * @param string $field The name of the column at the end of the operation. * @param string $type The type of the column at the end of the operation. TEXT, VARCHAR, CHAR, INTEGER, REAL, or TINYINT * @param string $size The size of that column type. As in VARCHAR($size), or INTEGER($size). * @param string $signed For numeric column types, whether that column is 'signed' or 'unsigned'. * @param string $default The new default value for the column. * @param string $null 'not null', or '' to allow nulls. * @param string $after Which column to insert this one after. Not supported on Postgres. * * @return boolean Wheter the operation succeeded. */ function table_column($table, $oldfield, $field, $type='integer', $size='10', $signed='unsigned', $default='0', $null='not null', $after='') { global $CFG, $db, $empty_rs_cache; if (!empty($empty_rs_cache[$table])) { // Clear the recordset cache because it's out of date unset($empty_rs_cache[$table]); } switch (strtolower($CFG->dbtype)) { case 'mysql': case 'mysqlt': switch (strtolower($type)) { case 'text': $type = 'TEXT'; $signed = ''; break; case 'integer': $type = 'INTEGER('. $size .')'; break; case 'varchar': $type = 'VARCHAR('. $size .')'; $signed = ''; break; case 'char': $type = 'CHAR('. $size .')'; $signed = ''; break; } if (!empty($oldfield)) { $operation = 'CHANGE '. $oldfield .' '. $field; } else { $operation = 'ADD '. $field; } $default = 'DEFAULT \''. $default .'\''; if (!empty($after)) { $after = 'AFTER `'. $after .'`'; } return execute_sql('ALTER TABLE '. $CFG->prefix . $table .' '. $operation .' '. $type .' '. $signed .' '. $default .' '. $null .' '. $after); case 'postgres7': // From Petri Asikainen //Check db-version $dbinfo = $db->ServerInfo(); $dbver = substr($dbinfo['version'],0,3); //to prevent conflicts with reserved words $realfield = '"'. $field .'"'; $field = '"'. $field .'_alter_column_tmp"'; $oldfield = '"'. $oldfield .'"'; switch (strtolower($type)) { case 'tinyint': case 'integer': if ($size <= 4) { $type = 'INT2'; } if ($size <= 10) { $type = 'INT'; } if ($size > 10) { $type = 'INT8'; } break; case 'varchar': $type = 'VARCHAR('. $size .')'; break; case 'char': $type = 'CHAR('. $size .')'; $signed = ''; break; } $default = '\''. $default .'\''; //After is not implemented in postgesql //if (!empty($after)) { // $after = "AFTER '$after'"; //} //Use transactions execute_sql('BEGIN'); //Always use temporary column execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ADD COLUMN '. $field .' '. $type); //Add default values execute_sql('UPDATE '. $CFG->prefix . $table .' SET '. $field .'='. $default); if ($dbver >= '7.3') { // modifying 'not null' is posible before 7.3 //update default values to table if (strtoupper($null) == 'NOT NULL') { execute_sql('UPDATE '. $CFG->prefix . $table .' SET '. $field .'='. $default .' WHERE '. $field .' IS NULL'); execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ALTER COLUMN '. $field .' SET '. $null); } else { execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ALTER COLUMN '. $field .' DROP NOT NULL'); } } execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ALTER COLUMN '. $field .' SET DEFAULT '. $default); if ( $oldfield != '""' ) { // We are changing the type of a column. This may require doing some casts... $casting = ''; $oldtype = column_type($table, $oldfield); $newtype = column_type($table, $field); // Do we need a cast? if($newtype == 'N' && $oldtype == 'C') { $casting = 'CAST(CAST('.$oldfield.' AS TEXT) AS REAL)'; } else if($newtype == 'I' && $oldtype == 'C') { $casting = 'CAST(CAST('.$oldfield.' AS TEXT) AS INTEGER)'; } else { $casting = $oldfield; } // Run the update query, casting as necessary execute_sql('UPDATE '. $CFG->prefix . $table .' SET '. $field .' = '. $casting); execute_sql('ALTER TABLE '. $CFG->prefix . $table .' DROP COLUMN '. $oldfield); } execute_sql('ALTER TABLE '. $CFG->prefix . $table .' RENAME COLUMN '. $field .' TO '. $realfield); return execute_sql('COMMIT'); default: switch (strtolower($type)) { case 'integer': $type = 'INTEGER'; break; case 'varchar': $type = 'VARCHAR'; break; } $default = 'DEFAULT \''. $default .'\''; if (!empty($after)) { $after = 'AFTER '. $after; } if (!empty($oldfield)) { execute_sql('ALTER TABLE '. $CFG->prefix . $table .' RENAME COLUMN '. $oldfield .' '. $field); } else { execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ADD COLUMN '. $field .' '. $type); } execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ALTER COLUMN '. $field .' SET '. $null); return execute_sql('ALTER TABLE '. $CFG->prefix . $table .' ALTER COLUMN '. $field .' SET '. $default); } } /** * This function IS NOT IMPLEMENTED. ONCE WE'LL BE USING RELATIONAL * INTEGRITY IT WILL BECOME MORE USEFUL. FOR NOW, JUST CALCULATE "OFFICIAL" * KEY NAMES WITHOUT ACCESSING TO DB AT ALL. * Given one XMLDBKey, the function returns the name of the key in DB (if exists) * of false if it doesn't exist * * @uses, $db * @param XMLDBTable the table to be searched * @param XMLDBKey the key to be searched * @return string key name of false */ function find_key_name($xmldb_table, $xmldb_key) { global $CFG, $db; /// Extract key columns $keycolumns = $xmldb_key->getFields(); /// Get list of keys in table /// first primaries (we aren't going to use this now, because the MetaPrimaryKeys is awful) ///TODO: To implement when we advance in relational integrity /// then uniques (note that Moodle, for now, shouldn't have any UNIQUE KEY for now, but unique indexes) ///TODO: To implement when we advance in relational integrity (note that AdoDB hasn't any MetaXXX for this. /// then foreign (note that Moodle, for now, shouldn't have any FOREIGN KEY for now, but indexes) ///TODO: To implement when we advance in relational integrity (note that AdoDB has one MetaForeignKeys() ///but it's far from perfect. /// TODO: To create the proper functions inside each generator to retrieve all the needed KEY info (name /// columns, reftable and refcolumns /// So all we do is to return the official name of the requested key without any confirmation!) $classname = 'XMLDB' . $CFG->dbtype; $generator = new $classname(); $generator->setPrefix($CFG->prefix); /// One exception, harcoded primary constraint names if ($generator->primary_key_name && $xmldb_key->getType() == XMLDB_KEY_PRIMARY) { return $generator->primary_key_name; } else { /// Calculate the name suffix switch ($xmldb_key->getType()) { case XMLDB_KEY_PRIMARY: $suffix = 'pk'; break; case XMLDB_KEY_UNIQUE: $suffix = 'uk'; break; case XMLDB_KEY_FOREIGN: $suffix = 'fk'; break; } /// And simply, return the oficial name return $generator->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), $suffix); } } /** * Given one XMLDBIndex, the function returns the name of the index in DB (if exists) * of false if it doesn't exist * * @uses, $db * @param XMLDBTable the table to be searched * @param XMLDBIndex the index to be searched * @return string index name of false */ function find_index_name($xmldb_table, $xmldb_index) { global $CFG, $db; /// Extract index columns $indcolumns = $xmldb_index->getFields(); /// Get list of indexes in table $indexes = array_change_key_case($db->MetaIndexes($CFG->prefix . $xmldb_table->getName(), CASE_LOWER)); /// Iterate over them looking for columns coincidence if ($indexes) { foreach ($indexes as $indexname => $index) { $columns = $index['columns']; /// Lower case column names $columns = array_flip($columns); $columns = array_change_key_case($columns, CASE_LOWER); $columns = array_flip($columns); /// Check if index matchs queried index $diferences = array_merge(array_diff($columns, $indcolumns), array_diff($indcolumns, $columns)); /// If no diferences, we have find the index if (empty($diferences)) { return $indexname; } } } /// Arriving here, index not found return false; } /** * This function will load one entire XMLDB file, generating all the needed * SQL statements, specific for each RDBMS ($CFG->dbtype) and, finally, it * will execute all those statements against the DB. * * @uses $CFG, $db * @param $file full path to the XML file to be used * @return boolean (true on success, false on error) */ function install_from_xmldb_file($file) { global $CFG, $db; $status = true; $xmldb_file = new XMLDBFile($file); if (!$xmldb_file->fileExists()) { return false; } $loaded = $xmldb_file->loadXMLStructure(); if (!$loaded || !$xmldb_file->isLoaded()) { return false; } $structure = $xmldb_file->getStructure(); if (!$sqlarr = $structure->getCreateStructureSQL($CFG->dbtype, $CFG->prefix, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr); } /** * This function will create the table passed as argument with all its * fields/keys/indexes/sequences, everything based in the XMLDB object * * @uses $CFG, $db * @param XMLDBTable table object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function create_table($table, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if(!$sqlarr = $table->getCreateTableSQL($CFG->dbtype, $CFG->prefix, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will drop the table passed as argument * and all the associated objects (keys, indexes, constaints, sequences, triggers) * will be dropped too. * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function drop_table($table, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if(!$sqlarr = $table->getDropTableSQL($CFG->dbtype, $CFG->prefix, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will add the field to the table passed as arguments * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBField field object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function add_field($table, $field, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if (strtolower(get_class($field)) != 'xmldbfield') { return false; } if(!$sqlarr = $table->getAddFieldSQL($CFG->dbtype, $CFG->prefix, $field, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will drop the field from the table passed as arguments * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBField field object (just the name is mandatory) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function drop_field($table, $field, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if (strtolower(get_class($field)) != 'xmldbfield') { return false; } if(!$sqlarr = $table->getDropFieldSQL($CFG->dbtype, $CFG->prefix, $field, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will change the precision of the field in the table passed as arguments * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBField field object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function change_field_precision($table, $field, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if (strtolower(get_class($field)) != 'xmldbfield') { return false; } if(!$sqlarr = $table->getAlterFieldSQL($CFG->dbtype, $CFG->prefix, $field, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will change the unsigned/signed of the field in the table passed as arguments * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBField field object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function change_field_unsigned($table, $field, $continue=true, $feedback=true) { /// Just a wrapper over change_field_precision. Does exactly the same processing return change_field_precision($table, $field, $continue, $feedback); } /** * This function will change the nullability of the field in the table passed as arguments * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBField field object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function change_field_notnull($table, $field, $continue=true, $feedback=true) { /// Just a wrapper over change_field_precision. Does exactly the same processing return change_field_precision($table, $field, $continue, $feedback); } /** * This function will change the default of the field in the table passed as arguments * One null value in the default field means delete the default * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBField field object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function change_field_default($table, $field, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if (strtolower(get_class($field)) != 'xmldbfield') { return false; } if(!$sqlarr = $table->getModifyDefaultSQL($CFG->dbtype, $CFG->prefix, $field, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will create the index in the table passed as arguments * Before creating the index, the function will check it doesn't exists * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBIndex index object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function add_index($table, $index, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if (strtolower(get_class($index)) != 'xmldbindex') { return false; } /// Check there isn't any index with the same fields if ($indexexists = find_index_name($table, $index)) { return true; //Index exists, nothing to do } if(!$sqlarr = $table->getAddIndexSQL($CFG->dbtype, $CFG->prefix, $index, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } /** * This function will drop the index in the table passed as arguments * Before dropping the index, the function will check it exists * * @uses $CFG, $db * @param XMLDBTable table object (just the name is mandatory) * @param XMLDBIndex index object (full specs are required) * @param boolean continue to specify if must continue on error (true) or stop (false) * @param boolean feedback to specify to show status info (true) or not (false) * @return boolean true on success, false on error */ function drop_index($table, $index, $continue=true, $feedback=true) { global $CFG, $db; $status = true; if (strtolower(get_class($table)) != 'xmldbtable') { return false; } if (strtolower(get_class($index)) != 'xmldbindex') { return false; } /// Check there is one index with the same fields if (!$indexexists = find_index_name($table, $index)) { return true; //Index doesn't exist, nothing to do } if(!$sqlarr = $table->getDropIndexSQL($CFG->dbtype, $CFG->prefix, $index, false)) { return true; //Empty array = nothing to do = no error } return execute_sql_arr($sqlarr, $continue, $feedback); } ?>