[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ddl/ -> sql_generator.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This class represent the base generator class where all the needed functions to generate proper SQL are defined.
  19   *
  20   * The rest of classes will inherit, by default, the same logic.
  21   * Functions will be overridden as needed to generate correct SQL.
  22   *
  23   * @package    core_ddl
  24   * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
  25   *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  /**
  32   * Abstract sql generator class, base for all db specific implementations.
  33   *
  34   * @package    core_ddl
  35   * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
  36   *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
  37   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  abstract class sql_generator {
  40  
  41      // Please, avoid editing this defaults in this base class!
  42      // It could change the behaviour of the rest of generators
  43      // that, by default, inherit this configuration.
  44      // To change any of them, do it in extended classes instead.
  45  
  46      /** @var string Used to quote names. */
  47      public $quote_string = '"';
  48  
  49      /** @var string To be automatically added at the end of each statement. */
  50      public $statement_end = ';';
  51  
  52      /** @var bool To decide if we want to quote all the names or only the reserved ones. */
  53      public $quote_all = false;
  54  
  55      /** @var bool To create all the integers as NUMBER(x) (also called DECIMAL, NUMERIC...). */
  56      public $integer_to_number = false;
  57  
  58      /** @var bool To create all the floats as NUMBER(x) (also called DECIMAL, NUMERIC...). */
  59      public $float_to_number   = false;
  60  
  61      /** @var string Proper type for NUMBER(x) in this DB. */
  62      public $number_type = 'NUMERIC';
  63  
  64      /** @var string To define the default to set for NOT NULLs CHARs without default (null=do nothing).*/
  65      public $default_for_char = null;
  66  
  67      /** @var bool To specify if the generator must use some DEFAULT clause to drop defaults.*/
  68      public $drop_default_value_required = false;
  69  
  70      /** @var string The DEFAULT clause required to drop defaults.*/
  71      public $drop_default_value = '';
  72  
  73      /** @var bool To decide if the default clause of each field must go after the null clause.*/
  74      public $default_after_null = true;
  75  
  76      /** @var bool To force the generator if NULL clauses must be specified. It shouldn't be necessary.*/
  77      public $specify_nulls = false;
  78  
  79      /** @var string To force primary key names to one string (null=no force).*/
  80      public $primary_key_name = null;
  81  
  82      /** @var bool True if the generator builds primary keys.*/
  83      public $primary_keys = true;
  84  
  85      /** @var bool True if the generator builds unique keys.*/
  86      public $unique_keys = false;
  87  
  88      /** @var bool True if the generator builds foreign keys.*/
  89      public $foreign_keys = false;
  90  
  91      /** @var string Template to drop PKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/
  92      public $drop_primary_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME';
  93  
  94      /** @var string Template to drop UKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/
  95      public $drop_unique_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME';
  96  
  97      /** @var string Template to drop FKs. 'TABLENAME' and 'KEYNAME' will be replaced from this template.*/
  98      public $drop_foreign_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME';
  99  
 100      /** @var bool True if the generator needs to add extra code to generate the sequence fields.*/
 101      public $sequence_extra_code = true;
 102  
 103      /** @var string The particular name for inline sequences in this generator.*/
 104      public $sequence_name = 'auto_increment';
 105  
 106      /** @var string|bool Different name for small (4byte) sequences or false if same.*/
 107      public $sequence_name_small = false;
 108  
 109      /**
 110       * @var bool To avoid outputting the rest of the field specs, leaving only the name and the sequence_name returned.
 111       * @see getFieldSQL()
 112       */
 113      public $sequence_only = false;
 114  
 115      /** @var bool True if the generator needs to add code for table comments.*/
 116      public $add_table_comments  = true;
 117  
 118      /** @var bool True if the generator needs to add the after clause for fields.*/
 119      public $add_after_clause = false;
 120  
 121      /**
 122       * @var bool True if the generator needs to prepend the prefix to all the key/index/sequence/trigger/check names.
 123       * @see $prefix
 124       */
 125      public $prefix_on_names = true;
 126  
 127      /** @var int Maximum length for key/index/sequence/trigger/check names (keep 30 for all!).*/
 128      public $names_max_length = 30;
 129  
 130      /** @var string Characters to be used as concatenation operator. If not defined, MySQL CONCAT function will be used.*/
 131      public $concat_character = '||';
 132  
 133      /** @var string SQL sentence to rename one table, both 'OLDNAME' and 'NEWNAME' keywords are dynamically replaced.*/
 134      public $rename_table_sql = 'ALTER TABLE OLDNAME RENAME TO NEWNAME';
 135  
 136      /** @var string SQL sentence to drop one table where the 'TABLENAME' keyword is dynamically replaced.*/
 137      public $drop_table_sql = 'DROP TABLE TABLENAME';
 138  
 139      /** @var string The SQL template to alter columns where the 'TABLENAME' and 'COLUMNSPECS' keywords are dynamically replaced.*/
 140      public $alter_column_sql = 'ALTER TABLE TABLENAME ALTER COLUMN COLUMNSPECS';
 141  
 142      /** @var bool The generator will skip the default clause on alter columns.*/
 143      public $alter_column_skip_default = false;
 144  
 145      /** @var bool The generator will skip the type clause on alter columns.*/
 146      public $alter_column_skip_type = false;
 147  
 148      /** @var bool The generator will skip the null/notnull clause on alter columns.*/
 149      public $alter_column_skip_notnull = false;
 150  
 151      /** @var string SQL sentence to rename one column where 'TABLENAME', 'OLDFIELDNAME' and 'NEWFIELDNAME' keywords are dynamically replaced.*/
 152      public $rename_column_sql = 'ALTER TABLE TABLENAME RENAME COLUMN OLDFIELDNAME TO NEWFIELDNAME';
 153  
 154      /** @var string SQL sentence to drop one index where 'TABLENAME', 'INDEXNAME' keywords are dynamically replaced.*/
 155      public $drop_index_sql = 'DROP INDEX INDEXNAME';
 156  
 157      /** @var string SQL sentence to rename one index where 'TABLENAME', 'OLDINDEXNAME' and 'NEWINDEXNAME' are dynamically replaced.*/
 158      public $rename_index_sql = 'ALTER INDEX OLDINDEXNAME RENAME TO NEWINDEXNAME';
 159  
 160      /** @var string SQL sentence to rename one key 'TABLENAME', 'OLDKEYNAME' and 'NEWKEYNAME' are dynamically replaced.*/
 161      public $rename_key_sql = 'ALTER TABLE TABLENAME CONSTRAINT OLDKEYNAME RENAME TO NEWKEYNAME';
 162  
 163      /** @var string The prefix to be used for all the DB objects.*/
 164      public $prefix;
 165  
 166      /** @var string List of reserved words (in order to quote them properly).*/
 167      public $reserved_words;
 168  
 169      /** @var moodle_database The moodle_database instance.*/
 170      public $mdb;
 171  
 172      /** @var Control existing temptables.*/
 173      protected $temptables;
 174  
 175      /**
 176       * Creates a new sql_generator.
 177       * @param moodle_database $mdb The moodle_database object instance.
 178       * @param moodle_temptables $temptables The optional moodle_temptables instance, null by default.
 179       */
 180      public function __construct($mdb, $temptables = null) {
 181          $this->prefix         = $mdb->get_prefix();
 182          $this->reserved_words = $this->getReservedWords();
 183          $this->mdb            = $mdb; // this creates circular reference - the other link must be unset when closing db
 184          $this->temptables     = $temptables;
 185      }
 186  
 187      /**
 188       * Releases all resources.
 189       */
 190      public function dispose() {
 191          $this->mdb = null;
 192      }
 193  
 194      /**
 195       * Given one string (or one array), ends it with $statement_end .
 196       *
 197       * @see $statement_end
 198       *
 199       * @param array|string $input SQL statement(s).
 200       * @return array|string
 201       */
 202      public function getEndedStatements($input) {
 203  
 204          if (is_array($input)) {
 205              foreach ($input as $key=>$content) {
 206                  $input[$key] = $this->getEndedStatements($content);
 207              }
 208              return $input;
 209          } else {
 210              $input = trim($input).$this->statement_end;
 211              return $input;
 212          }
 213      }
 214  
 215      /**
 216       * Given one xmldb_table, checks if it exists in DB (true/false).
 217       *
 218       * @param mixed $table The table to be searched (string name or xmldb_table instance).
 219       * @return boolean true/false
 220       */
 221      public function table_exists($table) {
 222          if (is_string($table)) {
 223              $tablename = $table;
 224          } else {
 225              // Calculate the name of the table
 226              $tablename = $table->getName();
 227          }
 228  
 229          // get all tables in moodle database
 230          $tables = $this->mdb->get_tables();
 231          $exists = in_array($tablename, $tables);
 232  
 233          return $exists;
 234      }
 235  
 236      /**
 237       * This function will return the SQL code needed to create db tables and statements.
 238       * @see xmldb_structure
 239       *
 240       * @param xmldb_structure $xmldb_structure An xmldb_structure instance.
 241       * @return array
 242       */
 243      public function getCreateStructureSQL($xmldb_structure) {
 244          $results = array();
 245  
 246          if ($tables = $xmldb_structure->getTables()) {
 247              foreach ($tables as $table) {
 248                  $results = array_merge($results, $this->getCreateTableSQL($table));
 249              }
 250          }
 251  
 252          return $results;
 253      }
 254  
 255      /**
 256       * Given one xmldb_table, this returns it's correct name, depending of all the parameterization.
 257       * eg: This appends $prefix to the table name.
 258       *
 259       * @see $prefix
 260       *
 261       * @param xmldb_table $xmldb_table The table whose name we want.
 262       * @param boolean $quoted To specify if the name must be quoted (if reserved word, only!).
 263       * @return string The correct name of the table.
 264       */
 265      public function getTableName(xmldb_table $xmldb_table, $quoted=true) {
 266          // Get the name
 267          $tablename = $this->prefix.$xmldb_table->getName();
 268  
 269          // Apply quotes optionally
 270          if ($quoted) {
 271              $tablename = $this->getEncQuoted($tablename);
 272          }
 273  
 274          return $tablename;
 275      }
 276  
 277      /**
 278       * Given one correct xmldb_table, returns the SQL statements
 279       * to create it (inside one array).
 280       *
 281       * @param xmldb_table $xmldb_table An xmldb_table instance.
 282       * @return array An array of SQL statements, starting with the table creation SQL followed
 283       * by any of its comments, indexes and sequence creation SQL statements.
 284       */
 285      public function getCreateTableSQL($xmldb_table) {
 286          if ($error = $xmldb_table->validateDefinition()) {
 287              throw new coding_exception($error);
 288          }
 289  
 290          $results = array();  //Array where all the sentences will be stored
 291  
 292          // Table header
 293          $table = 'CREATE TABLE ' . $this->getTableName($xmldb_table) . ' (';
 294  
 295          if (!$xmldb_fields = $xmldb_table->getFields()) {
 296              return $results;
 297          }
 298  
 299          $sequencefield = null;
 300  
 301          // Add the fields, separated by commas
 302          foreach ($xmldb_fields as $xmldb_field) {
 303              if ($xmldb_field->getSequence()) {
 304                  $sequencefield = $xmldb_field->getName();
 305              }
 306              $table .= "\n    " . $this->getFieldSQL($xmldb_table, $xmldb_field);
 307              $table .= ',';
 308          }
 309          // Add the keys, separated by commas
 310          if ($xmldb_keys = $xmldb_table->getKeys()) {
 311              foreach ($xmldb_keys as $xmldb_key) {
 312                  if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
 313                      $table .= "\nCONSTRAINT " . $keytext . ',';
 314                  }
 315                  // If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
 316                  if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE) {
 317                      //Duplicate the key
 318                      $xmldb_key->setType(XMLDB_KEY_UNIQUE);
 319                      if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
 320                          $table .= "\nCONSTRAINT " . $keytext . ',';
 321                      }
 322                  }
 323                  // make sure sequence field is unique
 324                  if ($sequencefield and $xmldb_key->getType() == XMLDB_KEY_PRIMARY) {
 325                      $fields = $xmldb_key->getFields();
 326                      $field = reset($fields);
 327                      if ($sequencefield === $field) {
 328                          $sequencefield = null;
 329                      }
 330                  }
 331              }
 332          }
 333          // throw error if sequence field does not have unique key defined
 334          if ($sequencefield) {
 335              throw new ddl_exception('ddsequenceerror', $xmldb_table->getName());
 336          }
 337  
 338          // Table footer, trim the latest comma
 339          $table = trim($table,',');
 340          $table .= "\n)";
 341  
 342          // Add the CREATE TABLE to results
 343          $results[] = $table;
 344  
 345          // Add comments if specified and it exists
 346          if ($this->add_table_comments && $xmldb_table->getComment()) {
 347              $comment = $this->getCommentSQL($xmldb_table);
 348              // Add the COMMENT to results
 349              $results = array_merge($results, $comment);
 350          }
 351  
 352          // Add the indexes (each one, one statement)
 353          if ($xmldb_indexes = $xmldb_table->getIndexes()) {
 354              foreach ($xmldb_indexes as $xmldb_index) {
 355                  //tables do not exist yet, which means indexed can not exist yet
 356                  if ($indextext = $this->getCreateIndexSQL($xmldb_table, $xmldb_index)) {
 357                      $results = array_merge($results, $indextext);
 358                  }
 359              }
 360          }
 361  
 362          // Also, add the indexes needed from keys, based on configuration (each one, one statement)
 363          if ($xmldb_keys = $xmldb_table->getKeys()) {
 364              foreach ($xmldb_keys as $xmldb_key) {
 365                  // If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
 366                  // automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
 367                  if (!$this->getKeySQL($xmldb_table, $xmldb_key) || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
 368                      // Create the interim index
 369                      $index = new xmldb_index('anyname');
 370                      $index->setFields($xmldb_key->getFields());
 371                      //tables do not exist yet, which means indexed can not exist yet
 372                      $createindex = false; //By default
 373                      switch ($xmldb_key->getType()) {
 374                          case XMLDB_KEY_UNIQUE:
 375                          case XMLDB_KEY_FOREIGN_UNIQUE:
 376                              $index->setUnique(true);
 377                              $createindex = true;
 378                              break;
 379                          case XMLDB_KEY_FOREIGN:
 380                              $index->setUnique(false);
 381                              $createindex = true;
 382                              break;
 383                      }
 384                      if ($createindex) {
 385                          if ($indextext = $this->getCreateIndexSQL($xmldb_table, $index)) {
 386                              // Add the INDEX to the array
 387                              $results = array_merge($results, $indextext);
 388                          }
 389                      }
 390                  }
 391              }
 392          }
 393  
 394          // Add sequence extra code if needed
 395          if ($this->sequence_extra_code) {
 396              // Iterate over fields looking for sequences
 397              foreach ($xmldb_fields as $xmldb_field) {
 398                  if ($xmldb_field->getSequence()) {
 399                      // returns an array of statements needed to create one sequence
 400                      $sequence_sentences = $this->getCreateSequenceSQL($xmldb_table, $xmldb_field);
 401                      // Add the SEQUENCE to the array
 402                      $results = array_merge($results, $sequence_sentences);
 403                  }
 404              }
 405          }
 406  
 407          return $results;
 408      }
 409  
 410      /**
 411       * Given one correct xmldb_index, returns the SQL statements
 412       * needed to create it (in array).
 413       *
 414       * @param xmldb_table $xmldb_table The xmldb_table instance to create the index on.
 415       * @param xmldb_index $xmldb_index The xmldb_index to create.
 416       * @return array An array of SQL statements to create the index.
 417       * @throws coding_exception Thrown if the xmldb_index does not validate with the xmldb_table.
 418       */
 419      public function getCreateIndexSQL($xmldb_table, $xmldb_index) {
 420          if ($error = $xmldb_index->validateDefinition($xmldb_table)) {
 421              throw new coding_exception($error);
 422          }
 423  
 424          $unique = '';
 425          $suffix = 'ix';
 426          if ($xmldb_index->getUnique()) {
 427              $unique = ' UNIQUE';
 428              $suffix = 'uix';
 429          }
 430  
 431          $index = 'CREATE' . $unique . ' INDEX ';
 432          $index .= $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_index->getFields()), $suffix);
 433          $index .= ' ON ' . $this->getTableName($xmldb_table);
 434          $index .= ' (' . implode(', ', $this->getEncQuoted($xmldb_index->getFields())) . ')';
 435  
 436          return array($index);
 437      }
 438  
 439      /**
 440       * Given one correct xmldb_field, returns the complete SQL line to create it.
 441       *
 442       * @param xmldb_table $xmldb_table The table related to $xmldb_field.
 443       * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.
 444       * @param string $skip_type_clause The type clause on alter columns, NULL by default.
 445       * @param string $skip_default_clause The default clause on alter columns, NULL by default.
 446       * @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.
 447       * @param string $specify_nulls_clause To force a specific null clause, NULL by default.
 448       * @param bool $specify_field_name Flag to specify fieldname in return.
 449       * @return string The field generating SQL statement.
 450       * @throws coding_exception Thrown when xmldb_field doesn't validate with the xmldb_table.
 451       */
 452      public function getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL, $specify_nulls_clause = NULL, $specify_field_name = true)  {
 453          if ($error = $xmldb_field->validateDefinition($xmldb_table)) {
 454              throw new coding_exception($error);
 455          }
 456  
 457          $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
 458          $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
 459          $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
 460          $specify_nulls_clause = is_null($specify_nulls_clause) ? $this->specify_nulls : $specify_nulls_clause;
 461  
 462          // First of all, convert integers to numbers if defined
 463          if ($this->integer_to_number) {
 464              if ($xmldb_field->getType() == XMLDB_TYPE_INTEGER) {
 465                  $xmldb_field->setType(XMLDB_TYPE_NUMBER);
 466              }
 467          }
 468          // Same for floats
 469          if ($this->float_to_number) {
 470              if ($xmldb_field->getType() == XMLDB_TYPE_FLOAT) {
 471                  $xmldb_field->setType(XMLDB_TYPE_NUMBER);
 472              }
 473          }
 474  
 475          $field = ''; // Let's accumulate the whole expression based on params and settings
 476          // The name
 477          if ($specify_field_name) {
 478              $field .= $this->getEncQuoted($xmldb_field->getName());
 479          }
 480          // The type and length only if we don't want to skip it
 481          if (!$skip_type_clause) {
 482              // The type and length
 483              $field .= ' ' . $this->getTypeSQL($xmldb_field->getType(), $xmldb_field->getLength(), $xmldb_field->getDecimals());
 484          }
 485          // note: unsigned is not supported any more since moodle 2.3, all numbers are signed
 486          // Calculate the not null clause
 487          $notnull = '';
 488          // Only if we don't want to skip it
 489          if (!$skip_notnull_clause) {
 490              if ($xmldb_field->getNotNull()) {
 491                  $notnull = ' NOT NULL';
 492              } else {
 493                  if ($specify_nulls_clause) {
 494                      $notnull = ' NULL';
 495                  }
 496              }
 497          }
 498          // Calculate the default clause
 499          $default_clause = '';
 500          if (!$skip_default_clause) { //Only if we don't want to skip it
 501              $default_clause = $this->getDefaultClause($xmldb_field);
 502          }
 503          // Based on default_after_null, set both clauses properly
 504          if ($this->default_after_null) {
 505              $field .= $notnull . $default_clause;
 506          } else {
 507              $field .= $default_clause . $notnull;
 508          }
 509          // The sequence
 510          if ($xmldb_field->getSequence()) {
 511              if($xmldb_field->getLength()<=9 && $this->sequence_name_small) {
 512                  $sequencename=$this->sequence_name_small;
 513              } else {
 514                  $sequencename=$this->sequence_name;
 515              }
 516              $field .= ' ' . $sequencename;
 517              if ($this->sequence_only) {
 518                  // We only want the field name and sequence name to be printed
 519                  // so, calculate it and return
 520                  $sql = $this->getEncQuoted($xmldb_field->getName()) . ' ' . $sequencename;
 521                  return $sql;
 522              }
 523          }
 524          return $field;
 525      }
 526  
 527      /**
 528       * Given one correct xmldb_key, returns its specs.
 529       *
 530       * @param xmldb_table $xmldb_table The table related to $xmldb_key.
 531       * @param xmldb_key $xmldb_key The xmldb_key's specifications requested.
 532       * @return string SQL statement about the xmldb_key.
 533       */
 534      public function getKeySQL($xmldb_table, $xmldb_key) {
 535  
 536          $key = '';
 537  
 538          switch ($xmldb_key->getType()) {
 539              case XMLDB_KEY_PRIMARY:
 540                  if ($this->primary_keys) {
 541                      if ($this->primary_key_name !== null) {
 542                          $key = $this->getEncQuoted($this->primary_key_name);
 543                      } else {
 544                          $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'pk');
 545                      }
 546                      $key .= ' PRIMARY KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
 547                  }
 548                  break;
 549              case XMLDB_KEY_UNIQUE:
 550                  if ($this->unique_keys) {
 551                      $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'uk');
 552                      $key .= ' UNIQUE (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
 553                  }
 554                  break;
 555              case XMLDB_KEY_FOREIGN:
 556              case XMLDB_KEY_FOREIGN_UNIQUE:
 557                  if ($this->foreign_keys) {
 558                      $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'fk');
 559                      $key .= ' FOREIGN KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
 560                      $key .= ' REFERENCES ' . $this->getEncQuoted($this->prefix . $xmldb_key->getRefTable());
 561                      $key .= ' (' . implode(', ', $this->getEncQuoted($xmldb_key->getRefFields())) . ')';
 562                  }
 563                  break;
 564          }
 565  
 566          return $key;
 567      }
 568  
 569      /**
 570       * Give one xmldb_field, returns the correct "default value" for the current configuration
 571       *
 572       * @param xmldb_field $xmldb_field The field.
 573       * @return The default value of the field.
 574       */
 575      public function getDefaultValue($xmldb_field) {
 576  
 577          $default = null;
 578  
 579          if ($xmldb_field->getDefault() !== NULL) {
 580              if ($xmldb_field->getType() == XMLDB_TYPE_CHAR ||
 581                  $xmldb_field->getType() == XMLDB_TYPE_TEXT) {
 582                      if ($xmldb_field->getDefault() === '') { // If passing empty default, use the $default_for_char one instead
 583                          $default = "'" . $this->default_for_char . "'";
 584                      } else {
 585                          $default = "'" . $this->addslashes($xmldb_field->getDefault()) . "'";
 586                      }
 587              } else {
 588                  $default = $xmldb_field->getDefault();
 589              }
 590          } else {
 591              // We force default '' for not null char columns without proper default
 592              // some day this should be out!
 593              if ($this->default_for_char !== NULL &&
 594                  $xmldb_field->getType() == XMLDB_TYPE_CHAR &&
 595                  $xmldb_field->getNotNull()) {
 596                  $default = "'" . $this->default_for_char . "'";
 597              } else {
 598                  // If the DB requires to explicity define some clause to drop one default, do it here
 599                  // never applying defaults to TEXT and BINARY fields
 600                  if ($this->drop_default_value_required &&
 601                      $xmldb_field->getType() != XMLDB_TYPE_TEXT &&
 602                      $xmldb_field->getType() != XMLDB_TYPE_BINARY && !$xmldb_field->getNotNull()) {
 603                      $default = $this->drop_default_value;
 604                  }
 605              }
 606          }
 607          return $default;
 608      }
 609  
 610      /**
 611       * Given one xmldb_field, returns the correct "default clause" for the current configuration.
 612       *
 613       * @param xmldb_field $xmldb_field The xmldb_field.
 614       * @return The SQL clause for generating the default value as in $xmldb_field.
 615       */
 616      public function getDefaultClause($xmldb_field) {
 617  
 618          $defaultvalue = $this->getDefaultValue ($xmldb_field);
 619  
 620          if ($defaultvalue !== null) {
 621              return ' DEFAULT ' . $defaultvalue;
 622          } else {
 623              return null;
 624          }
 625      }
 626  
 627      /**
 628       * Given one correct xmldb_table and the new name, returns the SQL statements
 629       * to rename it (inside one array).
 630       *
 631       * @param xmldb_table $xmldb_table The table to rename.
 632       * @param string $newname The new name to rename the table to.
 633       * @return array SQL statement(s) to rename the table.
 634       */
 635      public function getRenameTableSQL($xmldb_table, $newname) {
 636  
 637          $results = array();  //Array where all the sentences will be stored
 638  
 639          $newt = new xmldb_table($newname); //Temporal table for name calculations
 640  
 641          $rename = str_replace('OLDNAME', $this->getTableName($xmldb_table), $this->rename_table_sql);
 642          $rename = str_replace('NEWNAME', $this->getTableName($newt), $rename);
 643  
 644          $results[] = $rename;
 645  
 646          // Call to getRenameTableExtraSQL() override if needed
 647          $extra_sentences = $this->getRenameTableExtraSQL($xmldb_table, $newname);
 648          $results = array_merge($results, $extra_sentences);
 649  
 650          return $results;
 651      }
 652  
 653      /**
 654       * Given one correct xmldb_table and the new name, returns the SQL statements
 655       * to drop it (inside one array). Works also for temporary tables.
 656       *
 657       * @param xmldb_table $xmldb_table The table to drop.
 658       * @return array SQL statement(s) for dropping the specified table.
 659       */
 660      public function getDropTableSQL($xmldb_table) {
 661  
 662          $results = array();  //Array where all the sentences will be stored
 663  
 664          $drop = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_table_sql);
 665  
 666          $results[] = $drop;
 667  
 668          // call to getDropTableExtraSQL(), override if needed
 669          $extra_sentences = $this->getDropTableExtraSQL($xmldb_table);
 670          $results = array_merge($results, $extra_sentences);
 671  
 672          return $results;
 673      }
 674  
 675      /**
 676       * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add the field to the table.
 677       *
 678       * @param xmldb_table $xmldb_table The table related to $xmldb_field.
 679       * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.
 680       * @param string $skip_type_clause The type clause on alter columns, NULL by default.
 681       * @param string $skip_default_clause The default clause on alter columns, NULL by default.
 682       * @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.
 683       * @return array The SQL statement for adding a field to the table.
 684       */
 685      public function getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
 686  
 687          $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
 688          $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
 689          $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
 690  
 691          $results = array();
 692  
 693          // Get the quoted name of the table and field
 694          $tablename = $this->getTableName($xmldb_table);
 695  
 696          // Build the standard alter table add
 697          $sql = $this->getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause,
 698                                    $skip_default_clause,
 699                                    $skip_notnull_clause);
 700          $altertable = 'ALTER TABLE ' . $tablename . ' ADD ' . $sql;
 701          // Add the after clause if necessary
 702          if ($this->add_after_clause && $xmldb_field->getPrevious()) {
 703              $altertable .= ' AFTER ' . $this->getEncQuoted($xmldb_field->getPrevious());
 704          }
 705          $results[] = $altertable;
 706  
 707          return $results;
 708      }
 709  
 710      /**
 711       * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop the field from the table.
 712       *
 713       * @param xmldb_table $xmldb_table The table related to $xmldb_field.
 714       * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.
 715       * @return array The SQL statement for dropping a field from the table.
 716       */
 717      public function getDropFieldSQL($xmldb_table, $xmldb_field) {
 718  
 719          $results = array();
 720  
 721          // Get the quoted name of the table and field
 722          $tablename = $this->getTableName($xmldb_table);
 723          $fieldname = $this->getEncQuoted($xmldb_field->getName());
 724  
 725          // Build the standard alter table drop
 726          $results[] = 'ALTER TABLE ' . $tablename . ' DROP COLUMN ' . $fieldname;
 727  
 728          return $results;
 729      }
 730  
 731      /**
 732       * Given one xmldb_table and one xmldb_field, return the SQL statements needed to alter the field in the table.
 733       *
 734       * @param xmldb_table $xmldb_table The table related to $xmldb_field.
 735       * @param xmldb_field $xmldb_field The instance of xmldb_field to create the SQL from.
 736       * @param string $skip_type_clause The type clause on alter columns, NULL by default.
 737       * @param string $skip_default_clause The default clause on alter columns, NULL by default.
 738       * @param string $skip_notnull_clause The null/notnull clause on alter columns, NULL by default.
 739       * @return string The field altering SQL statement.
 740       */
 741      public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
 742  
 743          $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
 744          $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
 745          $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
 746  
 747          $results = array();
 748  
 749          // Get the quoted name of the table and field
 750          $tablename = $this->getTableName($xmldb_table);
 751          $fieldname = $this->getEncQuoted($xmldb_field->getName());
 752  
 753          // Build de alter sentence using the alter_column_sql template
 754          $alter = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->alter_column_sql);
 755          $colspec = $this->getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause,
 756                                        $skip_default_clause,
 757                                        $skip_notnull_clause,
 758                                        true);
 759          $alter = str_replace('COLUMNSPECS', $colspec, $alter);
 760  
 761          // Add the after clause if necessary
 762          if ($this->add_after_clause && $xmldb_field->getPrevious()) {
 763              $alter .= ' after ' . $this->getEncQuoted($xmldb_field->getPrevious());
 764          }
 765  
 766          // Build the standard alter table modify
 767          $results[] = $alter;
 768  
 769          return $results;
 770      }
 771  
 772      /**
 773       * Given one xmldb_table and one xmldb_field, return the SQL statements needed to modify the default of the field in the table.
 774       *
 775       * @param xmldb_table $xmldb_table The table related to $xmldb_field.
 776       * @param xmldb_field $xmldb_field The instance of xmldb_field to get the modified default value from.
 777       * @return array The SQL statement for modifying the default value.
 778       */
 779      public function getModifyDefaultSQL($xmldb_table, $xmldb_field) {
 780  
 781          $results = array();
 782  
 783          // Get the quoted name of the table and field
 784          $tablename = $this->getTableName($xmldb_table);
 785          $fieldname = $this->getEncQuoted($xmldb_field->getName());
 786  
 787          // Decide if we are going to create/modify or to drop the default
 788          if ($xmldb_field->getDefault() === null) {
 789              $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop
 790          } else {
 791              $results = $this->getCreateDefaultSQL($xmldb_table, $xmldb_field); //Create/modify
 792          }
 793  
 794          return $results;
 795      }
 796  
 797      /**
 798       * Given one correct xmldb_field and the new name, returns the SQL statements
 799       * to rename it (inside one array).
 800       *
 801       * @param xmldb_table $xmldb_table The table related to $xmldb_field.
 802       * @param xmldb_field $xmldb_field The instance of xmldb_field to get the renamed field from.
 803       * @param string $newname The new name to rename the field to.
 804       * @return array The SQL statements for renaming the field.
 805       */
 806      public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
 807  
 808          $results = array();  //Array where all the sentences will be stored
 809  
 810          // Although this is checked in database_manager::rename_field() - double check
 811          // that we aren't trying to rename one "id" field. Although it could be
 812          // implemented (if adding the necessary code to rename sequences, defaults,
 813          // triggers... and so on under each getRenameFieldExtraSQL() function, it's
 814          // better to forbid it, mainly because this field is the default PK and
 815          // in the future, a lot of FKs can be pointing here. So, this field, more
 816          // or less, must be considered immutable!
 817          if ($xmldb_field->getName() == 'id') {
 818              return array();
 819          }
 820  
 821          $rename = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_column_sql);
 822          $rename = str_replace('OLDFIELDNAME', $this->getEncQuoted($xmldb_field->getName()), $rename);
 823          $rename = str_replace('NEWFIELDNAME', $this->getEncQuoted($newname), $rename);
 824  
 825          $results[] = $rename;
 826  
 827          // Call to getRenameFieldExtraSQL(), override if needed
 828          $extra_sentences = $this->getRenameFieldExtraSQL($xmldb_table, $xmldb_field, $newname);
 829          $results = array_merge($results, $extra_sentences);
 830  
 831          return $results;
 832      }
 833  
 834      /**
 835       * Given one xmldb_table and one xmldb_key, return the SQL statements needed to add the key to the table
 836       * note that undelying indexes will be added as parametrised by $xxxx_keys and $xxxx_index parameters.
 837       *
 838       * @param xmldb_table $xmldb_table The table related to $xmldb_key.
 839       * @param xmldb_key $xmldb_key The xmldb_key to add.
 840       * @return array SQL statement to add the xmldb_key.
 841       */
 842      public function getAddKeySQL($xmldb_table, $xmldb_key) {
 843  
 844          $results = array();
 845  
 846          // Just use the CreateKeySQL function
 847          if ($keyclause = $this->getKeySQL($xmldb_table, $xmldb_key)) {
 848              $key = 'ALTER TABLE ' . $this->getTableName($xmldb_table) .
 849                 ' ADD CONSTRAINT ' . $keyclause;
 850              $results[] = $key;
 851          }
 852  
 853          // If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
 854          // automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
 855          if (!$keyclause || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
 856              // Only if they don't exist
 857              if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN) {      //Calculate type of index based on type ok key
 858                  $indextype = XMLDB_INDEX_NOTUNIQUE;
 859              } else {
 860                  $indextype = XMLDB_INDEX_UNIQUE;
 861              }
 862              $xmldb_index = new xmldb_index('anyname', $indextype, $xmldb_key->getFields());
 863              if (!$this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {
 864                  $results = array_merge($results, $this->getAddIndexSQL($xmldb_table, $xmldb_index));
 865              }
 866          }
 867  
 868          // If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
 869          if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
 870              //Duplicate the key
 871              $xmldb_key->setType(XMLDB_KEY_UNIQUE);
 872              $results = array_merge($results, $this->getAddKeySQL($xmldb_table, $xmldb_key));
 873          }
 874  
 875          // Return results
 876          return $results;
 877      }
 878  
 879      /**
 880       * Given one xmldb_table and one xmldb_index, return the SQL statements needed to drop the index from the table.
 881       *
 882       * @param xmldb_table $xmldb_table The table related to $xmldb_key.
 883       * @param xmldb_key $xmldb_key The xmldb_key to drop.
 884       * @return array SQL statement to drop the xmldb_key.
 885       */
 886      public function getDropKeySQL($xmldb_table, $xmldb_key) {
 887  
 888          $results = array();
 889  
 890          // Get the key name (note that this doesn't introspect DB, so could cause some problems sometimes!)
 891          // TODO: We'll need to overwrite the whole getDropKeySQL() method inside each DB to do the proper queries
 892          // against the dictionary or require ADOdb to support it or change the find_key_name() method to
 893          // perform DB introspection directly. But, for now, as we aren't going to enable referential integrity
 894          // it won't be a problem at all
 895          $dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);
 896  
 897          // Only if such type of key generation is enabled
 898          $dropkey = false;
 899          switch ($xmldb_key->getType()) {
 900              case XMLDB_KEY_PRIMARY:
 901                  if ($this->primary_keys) {
 902                      $template = $this->drop_primary_key;
 903                      $dropkey = true;
 904                  }
 905                  break;
 906              case XMLDB_KEY_UNIQUE:
 907                  if ($this->unique_keys) {
 908                      $template = $this->drop_unique_key;
 909                      $dropkey = true;
 910                  }
 911                  break;
 912              case XMLDB_KEY_FOREIGN_UNIQUE:
 913              case XMLDB_KEY_FOREIGN:
 914                  if ($this->foreign_keys) {
 915                      $template = $this->drop_foreign_key;
 916                      $dropkey = true;
 917                  }
 918                  break;
 919          }
 920          // If we have decided to drop the key, let's do it
 921          if ($dropkey) {
 922              // Replace TABLENAME, CONSTRAINTTYPE and KEYNAME as needed
 923              $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $template);
 924              $dropsql = str_replace('KEYNAME', $dbkeyname, $dropsql);
 925  
 926              $results[] = $dropsql;
 927          }
 928  
 929          // If we aren't dropping the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
 930          // automatically by the RDBMS) drop the underlying (created by us) index (if exists)
 931          if (!$dropkey || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
 932              // Only if they exist
 933              $xmldb_index = new xmldb_index('anyname', XMLDB_INDEX_UNIQUE, $xmldb_key->getFields());
 934              if ($this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {
 935                  $results = array_merge($results, $this->getDropIndexSQL($xmldb_table, $xmldb_index));
 936              }
 937          }
 938  
 939          // If the key is XMLDB_KEY_FOREIGN_UNIQUE, drop the UNIQUE too
 940          if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
 941              //Duplicate the key
 942              $xmldb_key->setType(XMLDB_KEY_UNIQUE);
 943              $results = array_merge($results, $this->getDropKeySQL($xmldb_table, $xmldb_key));
 944          }
 945  
 946          // Return results
 947          return $results;
 948      }
 949  
 950      /**
 951       * Given one xmldb_table and one xmldb_key, return the SQL statements needed to rename the key in the table
 952       * Experimental! Shouldn't be used at all!
 953       *
 954       * @param xmldb_table $xmldb_table The table related to $xmldb_key.
 955       * @param xmldb_key $xmldb_key The xmldb_key to rename.
 956       * @param string $newname The xmldb_key's new name.
 957       * @return array SQL statement to rename the xmldb_key.
 958       */
 959      public function getRenameKeySQL($xmldb_table, $xmldb_key, $newname) {
 960  
 961          $results = array();
 962  
 963          // Get the real key name
 964          $dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);
 965  
 966          // Check we are really generating this type of keys
 967          if (($xmldb_key->getType() == XMLDB_KEY_PRIMARY && !$this->primary_keys) ||
 968              ($xmldb_key->getType() == XMLDB_KEY_UNIQUE && !$this->unique_keys) ||
 969              ($xmldb_key->getType() == XMLDB_KEY_FOREIGN && !$this->foreign_keys) ||
 970              ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && !$this->unique_keys && !$this->foreign_keys)) {
 971              // We aren't generating this type of keys, delegate to child indexes
 972              $xmldb_index = new xmldb_index($xmldb_key->getName());
 973              $xmldb_index->setFields($xmldb_key->getFields());
 974              return $this->getRenameIndexSQL($xmldb_table, $xmldb_index, $newname);
 975          }
 976  
 977          // Arrived here so we are working with keys, lets rename them
 978          // Replace TABLENAME and KEYNAME as needed
 979          $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_key_sql);
 980          $renamesql = str_replace('OLDKEYNAME', $dbkeyname, $renamesql);
 981          $renamesql = str_replace('NEWKEYNAME', $newname, $renamesql);
 982  
 983          // Some DB doesn't support key renaming so this can be empty
 984          if ($renamesql) {
 985              $results[] = $renamesql;
 986          }
 987  
 988          return $results;
 989      }
 990  
 991      /**
 992       * Given one xmldb_table and one xmldb_index, return the SQL statements needed to add the index to the table.
 993       *
 994       * @param xmldb_table $xmldb_table The xmldb_table instance to add the index on.
 995       * @param xmldb_index $xmldb_index The xmldb_index to add.
 996       * @return array An array of SQL statements to add the index.
 997       */
 998      public function getAddIndexSQL($xmldb_table, $xmldb_index) {
 999  
1000          // Just use the CreateIndexSQL function
1001          return $this->getCreateIndexSQL($xmldb_table, $xmldb_index);
1002      }
1003  
1004      /**
1005       * Given one xmldb_table and one xmldb_index, return the SQL statements needed to drop the index from the table.
1006       *
1007       * @param xmldb_table $xmldb_table The xmldb_table instance to drop the index on.
1008       * @param xmldb_index $xmldb_index The xmldb_index to drop.
1009       * @return array An array of SQL statements to drop the index.
1010       */
1011      public function getDropIndexSQL($xmldb_table, $xmldb_index) {
1012  
1013          $results = array();
1014  
1015          // Get the real index name
1016          $dbindexnames = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index, true);
1017  
1018          // Replace TABLENAME and INDEXNAME as needed
1019          if ($dbindexnames) {
1020              foreach ($dbindexnames as $dbindexname) {
1021                  $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_index_sql);
1022                  $dropsql = str_replace('INDEXNAME', $this->getEncQuoted($dbindexname), $dropsql);
1023                  $results[] = $dropsql;
1024              }
1025          }
1026  
1027          return $results;
1028      }
1029  
1030      /**
1031       * Given one xmldb_table and one xmldb_index, return the SQL statements needed to rename the index in the table
1032       * Experimental! Shouldn't be used at all!
1033       *
1034       * @param xmldb_table $xmldb_table The xmldb_table instance to rename the index on.
1035       * @param xmldb_index $xmldb_index The xmldb_index to rename.
1036       * @param string $newname The xmldb_index's new name.
1037       * @return array An array of SQL statements to rename the index.
1038       */
1039      function getRenameIndexSQL($xmldb_table, $xmldb_index, $newname) {
1040          // Some DB doesn't support index renaming (MySQL) so this can be empty
1041          if (empty($this->rename_index_sql)) {
1042              return array();
1043          }
1044  
1045          // Get the real index name
1046          $dbindexname = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index);
1047          // Replace TABLENAME and INDEXNAME as needed
1048          $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_index_sql);
1049          $renamesql = str_replace('OLDINDEXNAME', $this->getEncQuoted($dbindexname), $renamesql);
1050          $renamesql = str_replace('NEWINDEXNAME', $this->getEncQuoted($newname), $renamesql);
1051  
1052          return array($renamesql);
1053      }
1054  
1055      /**
1056       * Given three strings (table name, list of fields (comma separated) and suffix),
1057       * create the proper object name quoting it if necessary.
1058       *
1059       * IMPORTANT: This function must be used to CALCULATE NAMES of objects TO BE CREATED,
1060       *            NEVER TO GUESS NAMES of EXISTING objects!!!
1061       *
1062       * @param string $tablename The table name.
1063       * @param string $fields A list of comma separated fields.
1064       * @param string $suffix A suffix for the object name.
1065       * @return string Object's name.
1066       */
1067      public function getNameForObject($tablename, $fields, $suffix='') {
1068  
1069          $name = '';
1070  
1071          // Implement one basic cache to avoid object name duplication
1072          // along all the request life, but never to return cached results
1073          // We need this because sql statements are created before executing
1074          // them, hence names doesn't exist "physically" yet in DB, so we need
1075          // to known which ones have been used
1076          static $used_names = array();
1077  
1078          // Use standard naming. See http://docs.moodle.org/en/XMLDB_key_and_index_naming
1079          $tablearr = explode ('_', $tablename);
1080          foreach ($tablearr as $table) {
1081              $name .= substr(trim($table),0,4);
1082          }
1083          $name .= '_';
1084          $fieldsarr = explode (',', $fields);
1085          foreach ($fieldsarr as $field) {
1086              $name .= substr(trim($field),0,3);
1087          }
1088          // Prepend the prefix
1089          $name = trim($this->prefix . $name);
1090  
1091          // Make sure name does not exceed the maximum name length and add suffix.
1092          $maxlengthwithoutsuffix = $this->names_max_length - strlen($suffix) - ($suffix ? 1 : 0);
1093          $namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix) . ($suffix ? ('_' . $suffix) : '');
1094  
1095          // If the calculated name is in the cache, or if we detect it by introspecting the DB let's modify if
1096          $counter = 1;
1097          while (in_array($namewithsuffix, $used_names) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {
1098              // Now iterate until not used name is found, incrementing the counter
1099              $counter++;
1100              $namewithsuffix = substr($name, 0, $maxlengthwithoutsuffix - strlen($counter)) .
1101                      $counter . ($suffix ? ('_' . $suffix) : '');
1102          }
1103  
1104          // Add the name to the cache
1105          $used_names[] = $namewithsuffix;
1106  
1107          // Quote it if necessary (reserved words)
1108          $namewithsuffix = $this->getEncQuoted($namewithsuffix);
1109  
1110          return $namewithsuffix;
1111      }
1112  
1113      /**
1114       * Given any string (or one array), enclose it by the proper quotes
1115       * if it's a reserved word
1116       *
1117       * @param string|array $input String to quote.
1118       * @return string Quoted string.
1119       */
1120      public function getEncQuoted($input) {
1121  
1122          if (is_array($input)) {
1123              foreach ($input as $key=>$content) {
1124                  $input[$key] = $this->getEncQuoted($content);
1125              }
1126              return $input;
1127          } else {
1128              // Always lowercase
1129              $input = strtolower($input);
1130              // if reserved or quote_all or has hyphens, quote it
1131              if ($this->quote_all || in_array($input, $this->reserved_words) || strpos($input, '-') !== false) {
1132                  $input = $this->quote_string . $input . $this->quote_string;
1133              }
1134              return $input;
1135          }
1136      }
1137  
1138      /**
1139       * Given one XMLDB Statement, build the needed SQL insert sentences to execute it.
1140       *
1141       * @param string $statement SQL statement.
1142       * @return array Array of sentences in the SQL statement.
1143       */
1144      function getExecuteInsertSQL($statement) {
1145  
1146           $results = array();  //Array where all the sentences will be stored
1147  
1148           if ($sentences = $statement->getSentences()) {
1149               foreach ($sentences as $sentence) {
1150                   // Get the list of fields
1151                   $fields = $statement->getFieldsFromInsertSentence($sentence);
1152                   // Get the values of fields
1153                   $values = $statement->getValuesFromInsertSentence($sentence);
1154                   // Look if we have some CONCAT value and transform it dynamically
1155                   foreach($values as $key => $value) {
1156                       // Trim single quotes
1157                       $value = trim($value,"'");
1158                       if (stristr($value, 'CONCAT') !== false){
1159                           // Look for data between parenthesis
1160                           preg_match("/CONCAT\s*\((.*)\)$/is", trim($value), $matches);
1161                           if (isset($matches[1])) {
1162                               $part = $matches[1];
1163                               // Convert the comma separated string to an array
1164                               $arr = xmldb_object::comma2array($part);
1165                               if ($arr) {
1166                                   $value = $this->getConcatSQL($arr);
1167                               }
1168                           }
1169                       }
1170                       // Values to be sent to DB must be properly escaped
1171                       $value = $this->addslashes($value);
1172                       // Back trimmed quotes
1173                       $value = "'" . $value . "'";
1174                       // Back to the array
1175                       $values[$key] = $value;
1176                   }
1177  
1178                   // Iterate over fields, escaping them if necessary
1179                   foreach($fields as $key => $field) {
1180                       $fields[$key] = $this->getEncQuoted($field);
1181                   }
1182                   // Build the final SQL sentence and add it to the array of results
1183               $sql = 'INSERT INTO ' . $this->getEncQuoted($this->prefix . $statement->getTable()) .
1184                           '(' . implode(', ', $fields) . ') ' .
1185                           'VALUES (' . implode(', ', $values) . ')';
1186                   $results[] = $sql;
1187               }
1188  
1189           }
1190           return $results;
1191      }
1192  
1193      /**
1194       * Given one array of elements, build the proper CONCAT expression, based
1195       * in the $concat_character setting. If such setting is empty, then
1196       * MySQL's CONCAT function will be used instead.
1197       *
1198       * @param array $elements An array of elements to concatenate.
1199       * @return mixed Returns the result of moodle_database::sql_concat() or false.
1200       * @uses moodle_database::sql_concat()
1201       * @uses call_user_func_array()
1202       */
1203      public function getConcatSQL($elements) {
1204  
1205          // Replace double quoted elements by single quotes
1206          foreach($elements as $key => $element) {
1207              $element = trim($element);
1208              if (substr($element, 0, 1) == '"' &&
1209                  substr($element, -1, 1) == '"') {
1210                      $elements[$key] = "'" . trim($element, '"') . "'";
1211              }
1212          }
1213  
1214          // Now call the standard $DB->sql_concat() DML function
1215          return call_user_func_array(array($this->mdb, 'sql_concat'), $elements);
1216      }
1217  
1218      /**
1219       * Returns the name (string) of the sequence used in the table for the autonumeric pk
1220       * Only some DB have this implemented.
1221       *
1222       * @param xmldb_table $xmldb_table The xmldb_table instance.
1223       * @return bool Returns the sequence from the DB or false.
1224       */
1225      public function getSequenceFromDB($xmldb_table) {
1226          return false;
1227      }
1228  
1229      /**
1230       * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg).
1231       *
1232       * (MySQL requires the whole xmldb_table object to be specified, so we add it always)
1233       *
1234       * This is invoked from getNameForObject().
1235       * Only some DB have this implemented.
1236       *
1237       * @param string $object_name The object's name to check for.
1238       * @param string $type The object's type (pk, uk, fk, ck, ix, uix, seq, trg).
1239       * @param string $table_name The table's name to check in
1240       * @return bool If such name is currently in use (true) or no (false)
1241       */
1242      public function isNameInUse($object_name, $type, $table_name) {
1243          return false; //For generators not implementing introspection,
1244                        //we always return with the name being free to be used
1245      }
1246  
1247  
1248  // ====== FOLLOWING FUNCTION MUST BE CUSTOMISED BY ALL THE XMLDGenerator classes ========
1249  
1250      /**
1251       * Reset a sequence to the id field of a table.
1252       *
1253       * @param xmldb_table|string $table name of table or the table object.
1254       * @return array of sql statements
1255       */
1256      public abstract function getResetSequenceSQL($table);
1257  
1258      /**
1259       * Given one correct xmldb_table, returns the SQL statements
1260       * to create temporary table (inside one array).
1261       *
1262       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1263       * @return array of sql statements
1264       */
1265      abstract public function getCreateTempTableSQL($xmldb_table);
1266  
1267      /**
1268       * Given one XMLDB Type, length and decimals, returns the DB proper SQL type.
1269       *
1270       * @param int $xmldb_type The xmldb_type defined constant. XMLDB_TYPE_INTEGER and other XMLDB_TYPE_* constants.
1271       * @param int $xmldb_length The length of that data type.
1272       * @param int $xmldb_decimals The decimal places of precision of the data type.
1273       * @return string The DB defined data type.
1274       */
1275      public abstract function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null);
1276  
1277      /**
1278       * Returns the code (array of statements) needed to execute extra statements on field rename.
1279       *
1280       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1281       * @param xmldb_field $xmldb_field The xmldb_field object instance.
1282       * @return array Array of extra SQL statements to run with a field being renamed.
1283       */
1284      public function getRenameFieldExtraSQL($xmldb_table, $xmldb_field) {
1285          return array();
1286      }
1287  
1288      /**
1289       * Returns the code (array of statements) needed
1290       * to create one sequence for the xmldb_table and xmldb_field passed in.
1291       *
1292       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1293       * @param xmldb_field $xmldb_field The xmldb_field object instance.
1294       * @return array Array of SQL statements to create the sequence.
1295       */
1296      public function getCreateSequenceSQL($xmldb_table, $xmldb_field) {
1297          return array();
1298      }
1299  
1300      /**
1301       * Returns the code (array of statements) needed to add one comment to the table.
1302       *
1303       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1304       * @return array Array of SQL statements to add one comment to the table.
1305       */
1306      public abstract function getCommentSQL($xmldb_table);
1307  
1308      /**
1309       * Returns the code (array of statements) needed to execute extra statements on table rename.
1310       *
1311       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1312       * @param string $newname The new name for the table.
1313       * @return array Array of extra SQL statements to rename a table.
1314       */
1315      public function getRenameTableExtraSQL($xmldb_table, $newname) {
1316          return array();
1317      }
1318  
1319      /**
1320       * Returns the code (array of statements) needed to execute extra statements on table drop
1321       *
1322       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1323       * @return array Array of extra SQL statements to drop a table.
1324       */
1325      public function getDropTableExtraSQL($xmldb_table) {
1326          return array();
1327      }
1328  
1329      /**
1330       * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default
1331       * (usually invoked from getModifyDefaultSQL()
1332       *
1333       * Note that this method may be dropped in future.
1334       *
1335       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1336       * @param xmldb_field $xmldb_field The xmldb_field object instance.
1337       * @return array Array of SQL statements to create a field's default.
1338       *
1339       * @todo MDL-31147 Moodle 2.1 - Drop getDropDefaultSQL()
1340       */
1341      public abstract function getDropDefaultSQL($xmldb_table, $xmldb_field);
1342  
1343      /**
1344       * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default
1345       * (usually invoked from getModifyDefaultSQL()
1346       *
1347       * @param xmldb_table $xmldb_table The xmldb_table object instance.
1348       * @param xmldb_field $xmldb_field The xmldb_field object instance.
1349       * @return array Array of SQL statements to create a field's default.
1350       */
1351      public abstract function getCreateDefaultSQL($xmldb_table, $xmldb_field);
1352  
1353      /**
1354       * Returns an array of reserved words (lowercase) for this DB
1355       * You MUST provide the real list for each DB inside every XMLDB class.
1356       * @return array An array of database specific reserved words.
1357       * @throws coding_exception Thrown if not implemented for the specific DB.
1358       */
1359      public static function getReservedWords() {
1360          throw new coding_exception('getReservedWords() method needs to be overridden in each subclass of sql_generator');
1361      }
1362  
1363      /**
1364       * Returns all reserved words in supported databases.
1365       * Reserved words should be lowercase.
1366       * @return array ('word'=>array(databases))
1367       */
1368      public static function getAllReservedWords() {
1369          global $CFG;
1370  
1371          $generators = array('mysql', 'postgres', 'oracle', 'mssql');
1372          $reserved_words = array();
1373  
1374          foreach($generators as $generator) {
1375              $class = $generator . '_sql_generator';
1376              require_once("$CFG->libdir/ddl/$class.php");
1377              foreach (call_user_func(array($class, 'getReservedWords')) as $word) {
1378                  $reserved_words[$word][] = $generator;
1379              }
1380          }
1381          ksort($reserved_words);
1382          return $reserved_words;
1383      }
1384  
1385      /**
1386       * Adds slashes to string.
1387       * @param string $s
1388       * @return string The escaped string.
1389       */
1390      public function addslashes($s) {
1391          // do not use php addslashes() because it depends on PHP quote settings!
1392          $s = str_replace('\\','\\\\',$s);
1393          $s = str_replace("\0","\\\0", $s);
1394          $s = str_replace("'",  "\\'", $s);
1395          return $s;
1396      }
1397  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1