moodle/lib/ddllib.php

653 lines
24 KiB
PHP

<?php // $Id$
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.com //
// //
// Copyright (C) 2001-3001 Martin Dougiamas http://dougiamas.com //
// (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
// //
// This program 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 2 of the License, or //
// (at your option) any later version. //
// //
// This program 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: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
// This library includes all the required functions used to handle the DB
// structure (DDL) independently of the underlying RDBMS in use. All the functions
// rely on the XMLDBDriver classes to be able to generate the correct SQL
// syntax needed by each DB.
//
// To define any structure to be created we'll use the schema defined
// by the XMLDB classes, for tables, fields, indexes, keys and other
// statements instead of direct handling of SQL sentences.
//
// This library should be used, exclusively, by the installation and
// upgrade process of Moodle.
//
// For further documentation, visit http://docs.moodle.org/en/DDL_functions
/// Add required XMLDB constants
require_once($CFG->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);
}
?>