MDL-30709: Fixed bug with inserting files from Recent files repository
[moodle.git] / lib / ddl / sql_generator.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
19 /**
20  * This class represent the base generator class where all the
21  * needed functions to generate proper SQL are defined.
22  *
23  * The rest of classes will inherit, by default, the same logic.
24  * Functions will be overridden as needed to generate correct SQL.
25  *
26  * @package    core
27  * @subpackage ddl
28  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
29  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
30  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31  */
33 defined('MOODLE_INTERNAL') || die();
35 /**
36  * Abstract sql generator class, base for all db specific implementations.
37  */
38 abstract class sql_generator {
40 /// Please, avoid editing this defaults in this base class!
41 /// It could change the behaviour of the rest of generators
42 /// that, by default, inherit this configuration.
43 /// To change any of them, do it in extended classes instead.
45     public $quote_string = '"';   // String used to quote names
47     public $statement_end = ';'; // String to be automatically added at the end of each statement
49     public $quote_all    = false; // To decide if we want to quote all the names or only the reserved ones
51     public $integer_to_number = false;  // To create all the integers as NUMBER(x) (also called DECIMAL, NUMERIC...)
52     public $float_to_number   = false;  // To create all the floats as NUMBER(x) (also called DECIMAL, NUMERIC...)
54     public $number_type = 'NUMERIC';    // Proper type for NUMBER(x) in this DB
56     public $unsigned_allowed = true;    // To define in the generator must handle unsigned information
57     public $default_for_char = null;      // To define the default to set for NOT NULLs CHARs without default (null=do nothing)
59     public $drop_default_value_required = false; //To specify if the generator must use some DEFAULT clause to drop defaults
60     public $drop_default_value = ''; //The DEFAULT clause required to drop defaults
62     public $default_after_null = true;  //To decide if the default clause of each field must go after the null clause
64     public $specify_nulls = false;  //To force the generator if NULL clauses must be specified. It shouldn't be necessary
65                                  //but some mssql drivers require them or everything is created as NOT NULL :-(
67     public $primary_key_name = null; //To force primary key names to one string (null=no force)
69     public $primary_keys = true;  // Does the generator build primary keys
70     public $unique_keys = false;  // Does the generator build unique keys
71     public $foreign_keys = false; // Does the generator build foreign keys
73     public $drop_primary_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop PKs
74                                // with automatic replace for TABLENAME and KEYNAME
76     public $drop_unique_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop UKs
77                                // with automatic replace for TABLENAME and KEYNAME
79     public $drop_foreign_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop FKs
80                                // with automatic replace for TABLENAME and KEYNAME
82     public $sequence_extra_code = true; //Does the generator need to add extra code to generate the sequence fields
83     public $sequence_name = 'auto_increment'; //Particular name for inline sequences in this generator
84     public $sequence_name_small = false; //Different name for small (4byte) sequences or false if same
85     public $sequence_only = false; //To avoid to output the rest of the field specs, leaving only the name and the sequence_name publiciable
87     public $add_table_comments  = true;  // Does the generator need to add code for table comments
89     public $add_after_clause = false; // Does the generator need to add the after clause for fields
91     public $prefix_on_names = true; //Does the generator need to prepend the prefix to all the key/index/sequence/trigger/check names
93     public $names_max_length = 30; //Max length for key/index/sequence/trigger/check names (keep 30 for all!)
95     public $concat_character = '||'; //Characters to be used as concatenation operator. If not defined
96                                   //MySQL CONCAT function will be used
98     public $rename_table_sql = 'ALTER TABLE OLDNAME RENAME TO NEWNAME'; //SQL sentence to rename one table, both
99                                   //OLDNAME and NEWNAME are dynamically replaced
101     public $drop_table_sql = 'DROP TABLE TABLENAME'; //SQL sentence to drop one table
102                                   //TABLENAME is dynamically replaced
104     public $alter_column_sql = 'ALTER TABLE TABLENAME ALTER COLUMN COLUMNSPECS'; //The SQL template to alter columns
106     public $alter_column_skip_default = false; //The generator will skip the default clause on alter columns
108     public $alter_column_skip_type = false; //The generator will skip the type clause on alter columns
110     public $alter_column_skip_notnull = false; //The generator will skip the null/notnull clause on alter columns
112     public $rename_column_sql = 'ALTER TABLE TABLENAME RENAME COLUMN OLDFIELDNAME TO NEWFIELDNAME';
113                                   ///TABLENAME, OLDFIELDNAME and NEWFIELDNAME are dyanmically replaced
115     public $drop_index_sql = 'DROP INDEX INDEXNAME'; //SQL sentence to drop one index
116                                   //TABLENAME, INDEXNAME are dynamically replaced
118     public $rename_index_sql = 'ALTER INDEX OLDINDEXNAME RENAME TO NEWINDEXNAME'; //SQL sentence to rename one index
119                                   //TABLENAME, OLDINDEXNAME, NEWINDEXNAME are dynamically replaced
121     public $rename_key_sql = 'ALTER TABLE TABLENAME CONSTRAINT OLDKEYNAME RENAME TO NEWKEYNAME'; //SQL sentence to rename one key
122                                   //TABLENAME, OLDKEYNAME, NEWKEYNAME are dynamically replaced
124     public $prefix;         // Prefix to be used for all the DB objects
126     public $reserved_words; // List of reserved words (in order to quote them properly)
128     public $mdb;
130     protected $temptables; // Control existing temptables
132     /**
133      * Creates new sql_generator
134      * @param object moodle_database instance
135      */
136     public function __construct($mdb, $temptables = null) {
137         $this->prefix         = $mdb->get_prefix();
138         $this->reserved_words = $this->getReservedWords();
139         $this->mdb            = $mdb; // this creates circular reference - the other link must be unset when closing db
140         $this->temptables     = $temptables;
141     }
143     /**
144      * Release all resources
145      */
146     public function dispose() {
147         $this->mdb = null;
148     }
150     /**
151      * Given one string (or one array), ends it with statement_end
152      */
153     public function getEndedStatements($input) {
155         if (is_array($input)) {
156             foreach ($input as $key=>$content) {
157                 $input[$key] = $this->getEndedStatements($content);
158             }
159             return $input;
160         } else {
161             $input = trim($input).$this->statement_end;
162             return $input;
163         }
164     }
166     /**
167      * Given one xmldb_table, check if it exists in DB (true/false)
168      *
169      * @param mixed the table to be searched (string name or xmldb_table instance)
170      * @return boolean true/false
171      */
172     public function table_exists($table) {
173         if (is_string($table)) {
174             $tablename = $table;
175         } else {
176         /// Calculate the name of the table
177             $tablename = $table->getName();
178         }
180     /// get all tables in moodle database
181         $tables = $this->mdb->get_tables();
182         $exists = in_array($tablename, $tables);
184         return $exists;
185     }
187     /**
188      * This function will return the SQL code needed to create db tables and statements
189      */
190     public function getCreateStructureSQL($xmldb_structure) {
191         $results = array();
193         if ($tables = $xmldb_structure->getTables()) {
194             foreach ($tables as $table) {
195                 $results = array_merge($results, $this->getCreateTableSQL($table));
196             }
197         }
199         return $results;
200     }
202     /**
203      * Given one xmldb_table, returns it's correct name, depending of all the parametrization
204      *
205      * @param xmldb_table table whose name we want
206      * @param boolean to specify if the name must be quoted (if reserved word, only!)
207      * @return string the correct name of the table
208      */
209     public function getTableName(xmldb_table $xmldb_table, $quoted=true) {
210     /// Get the name
211         $tablename = $this->prefix.$xmldb_table->getName();
213     /// Apply quotes optionally
214         if ($quoted) {
215             $tablename = $this->getEncQuoted($tablename);
216         }
218         return $tablename;
219     }
221     /**
222      * Given one correct xmldb_table, returns the SQL statements
223      * to create it (inside one array)
224      */
225     public function getCreateTableSQL($xmldb_table) {
227         $results = array();  //Array where all the sentences will be stored
229     /// Table header
230         $table = 'CREATE TABLE ' . $this->getTableName($xmldb_table) . ' (';
232         if (!$xmldb_fields = $xmldb_table->getFields()) {
233             return $results;
234         }
236         $sequencefield = null;
238     /// Add the fields, separated by commas
239         foreach ($xmldb_fields as $xmldb_field) {
240             if ($xmldb_field->getSequence()) {
241                 $sequencefield = $xmldb_field->getName();
242             }
243             $table .= "\n    " . $this->getFieldSQL($xmldb_table, $xmldb_field);
244             $table .= ',';
245         }
246     /// Add the keys, separated by commas
247         if ($xmldb_keys = $xmldb_table->getKeys()) {
248             foreach ($xmldb_keys as $xmldb_key) {
249                 if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
250                     $table .= "\nCONSTRAINT " . $keytext . ',';
251                 }
252             /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
253                 if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE) {
254                 ///Duplicate the key
255                     $xmldb_key->setType(XMLDB_KEY_UNIQUE);
256                     if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
257                         $table .= "\nCONSTRAINT " . $keytext . ',';
258                     }
259                 }
260             /// make sure sequence field is unique
261                 if ($sequencefield and $xmldb_key->getType() == XMLDB_KEY_PRIMARY) {
262                     $field = reset($xmldb_key->getFields());
263                     if ($sequencefield === $field) {
264                         $sequencefield = null;
265                     }
266                 }
267             }
268         }
269     /// throw error if sequence field does not have unique key defined
270         if ($sequencefield) {
271             throw new ddl_exception('ddsequenceerror', $xmldb_table->getName());
272         }
274     /// Table footer, trim the latest comma
275         $table = trim($table,',');
276         $table .= "\n)";
278     /// Add the CREATE TABLE to results
279         $results[] = $table;
281     /// Add comments if specified and it exists
282         if ($this->add_table_comments && $xmldb_table->getComment()) {
283             $comment = $this->getCommentSQL($xmldb_table);
284         /// Add the COMMENT to results
285             $results = array_merge($results, $comment);
286         }
288     /// Add the indexes (each one, one statement)
289         if ($xmldb_indexes = $xmldb_table->getIndexes()) {
290             foreach ($xmldb_indexes as $xmldb_index) {
291             ///tables do not exist yet, which means indexed can not exist yet
292                 if ($indextext = $this->getCreateIndexSQL($xmldb_table, $xmldb_index)) {
293                     $results = array_merge($results, $indextext);
294                 }
295             }
296         }
298     /// Also, add the indexes needed from keys, based on configuration (each one, one statement)
299         if ($xmldb_keys = $xmldb_table->getKeys()) {
300             foreach ($xmldb_keys as $xmldb_key) {
301             /// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
302             /// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
303                 if (!$this->getKeySQL($xmldb_table, $xmldb_key) || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
304                 /// Create the interim index
305                     $index = new xmldb_index('anyname');
306                     $index->setFields($xmldb_key->getFields());
307                 ///tables do not exist yet, which means indexed can not exist yet
308                     $createindex = false; //By default
309                     switch ($xmldb_key->getType()) {
310                         case XMLDB_KEY_UNIQUE:
311                         case XMLDB_KEY_FOREIGN_UNIQUE:
312                             $index->setUnique(true);
313                             $createindex = true;
314                             break;
315                         case XMLDB_KEY_FOREIGN:
316                             $index->setUnique(false);
317                             $createindex = true;
318                             break;
319                     }
320                     if ($createindex) {
321                         if ($indextext = $this->getCreateIndexSQL($xmldb_table, $index)) {
322                         /// Add the INDEX to the array
323                             $results = array_merge($results, $indextext);
324                         }
325                     }
326                 }
327             }
328         }
330     /// Add sequence extra code if needed
331         if ($this->sequence_extra_code) {
332         /// Iterate over fields looking for sequences
333             foreach ($xmldb_fields as $xmldb_field) {
334                 if ($xmldb_field->getSequence()) {
335                 /// returns an array of statements needed to create one sequence
336                     $sequence_sentences = $this->getCreateSequenceSQL($xmldb_table, $xmldb_field);
337                 /// Add the SEQUENCE to the array
338                     $results = array_merge($results, $sequence_sentences);
339                 }
340             }
341         }
343         return $results;
344     }
346     /**
347      * Given one correct xmldb_index, returns the SQL statements
348      * needed to create it (in array)
349      */
350     public function getCreateIndexSQL($xmldb_table, $xmldb_index) {
351         if ($error = $xmldb_index->validateDefinition($xmldb_table)) {
352             throw new coding_exception($error);
353         }
355         $unique = '';
356         $suffix = 'ix';
357         if ($xmldb_index->getUnique()) {
358             $unique = ' UNIQUE';
359             $suffix = 'uix';
360         }
362         $index = 'CREATE' . $unique . ' INDEX ';
363         $index .= $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_index->getFields()), $suffix);
364         $index .= ' ON ' . $this->getTableName($xmldb_table);
365         $index .= ' (' . implode(', ', $this->getEncQuoted($xmldb_index->getFields())) . ')';
367         return array($index);
368     }
370     /**
371      * Given one correct xmldb_field, returns the complete SQL line to create it
372      */
373     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)  {
374         if ($error = $xmldb_field->validateDefinition($xmldb_table)) {
375             throw new coding_exception($error);
376         }
378         $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
379         $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
380         $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
381         $specify_nulls_clause = is_null($specify_nulls_clause) ? $this->specify_nulls : $specify_nulls_clause;
383     /// First of all, convert integers to numbers if defined
384         if ($this->integer_to_number) {
385             if ($xmldb_field->getType() == XMLDB_TYPE_INTEGER) {
386                 $xmldb_field->setType(XMLDB_TYPE_NUMBER);
387             }
388         }
389     /// Same for floats
390         if ($this->float_to_number) {
391             if ($xmldb_field->getType() == XMLDB_TYPE_FLOAT) {
392                 $xmldb_field->setType(XMLDB_TYPE_NUMBER);
393             }
394         }
396         $field = ''; // Let's accumulate the whole expression based on params and settings
397     /// The name
398         if ($specify_field_name) {
399             $field .= $this->getEncQuoted($xmldb_field->getName());
400         }
401     /// The type and length only if we don't want to skip it
402         if (!$skip_type_clause) {
403         /// The type and length
404             $field .= ' ' . $this->getTypeSQL($xmldb_field->getType(), $xmldb_field->getLength(), $xmldb_field->getDecimals());
405         }
406     /// The unsigned if supported
407         if ($this->unsigned_allowed && ($xmldb_field->getType() == XMLDB_TYPE_INTEGER ||
408                                       $xmldb_field->getType() == XMLDB_TYPE_NUMBER ||
409                                       $xmldb_field->getType() == XMLDB_TYPE_FLOAT)) {
410             if ($xmldb_field->getUnsigned()) {
411                 $field .= ' unsigned';
412             }
413         }
414     /// Calculate the not null clause
415         $notnull = '';
416     /// Only if we don't want to skip it
417         if (!$skip_notnull_clause) {
418             if ($xmldb_field->getNotNull()) {
419                 $notnull = ' NOT NULL';
420             } else {
421                 if ($specify_nulls_clause) {
422                     $notnull = ' NULL';
423                 }
424             }
425         }
426     /// Calculate the default clause
427         $default_clause = '';
428         if (!$skip_default_clause) { //Only if we don't want to skip it
429             $default_clause = $this->getDefaultClause($xmldb_field);
430         }
431     /// Based on default_after_null, set both clauses properly
432         if ($this->default_after_null) {
433             $field .= $notnull . $default_clause;
434         } else {
435             $field .= $default_clause . $notnull;
436         }
437     /// The sequence
438         if ($xmldb_field->getSequence()) {
439             if($xmldb_field->getLength()<=9 && $this->sequence_name_small) {
440                 $sequencename=$this->sequence_name_small;
441             } else {
442                 $sequencename=$this->sequence_name;
443             }
444             $field .= ' ' . $sequencename;
445             if ($this->sequence_only) {
446             /// We only want the field name and sequence name to be printed
447             /// so, calculate it and return
448                 $sql = $this->getEncQuoted($xmldb_field->getName()) . ' ' . $sequencename;
449                 return $sql;
450             }
451         }
452         return $field;
453     }
455     /**
456      * Given one correct xmldb_key, returns its specs
457      */
458     public function getKeySQL($xmldb_table, $xmldb_key) {
460         $key = '';
462         switch ($xmldb_key->getType()) {
463             case XMLDB_KEY_PRIMARY:
464                 if ($this->primary_keys) {
465                     if ($this->primary_key_name !== null) {
466                         $key = $this->getEncQuoted($this->primary_key_name);
467                     } else {
468                         $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'pk');
469                     }
470                     $key .= ' PRIMARY KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
471                 }
472                 break;
473             case XMLDB_KEY_UNIQUE:
474                 if ($this->unique_keys) {
475                     $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'uk');
476                     $key .= ' UNIQUE (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
477                 }
478                 break;
479             case XMLDB_KEY_FOREIGN:
480             case XMLDB_KEY_FOREIGN_UNIQUE:
481                 if ($this->foreign_keys) {
482                     $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'fk');
483                     $key .= ' FOREIGN KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
484                     $key .= ' REFERENCES ' . $this->getEncQuoted($this->prefix . $xmldb_key->getRefTable());
485                     $key .= ' (' . implode(', ', $this->getEncQuoted($xmldb_key->getRefFields())) . ')';
486                 }
487                 break;
488         }
490         return $key;
491     }
493     /**
494      * Give one xmldb_field, returns the correct "default value" for the current configuration
495      */
496     public function getDefaultValue($xmldb_field) {
498         $default = null;
500         if ($xmldb_field->getDefault() !== NULL) {
501             if ($xmldb_field->getType() == XMLDB_TYPE_CHAR ||
502                 $xmldb_field->getType() == XMLDB_TYPE_TEXT) {
503                     if ($xmldb_field->getDefault() === '') { // If passing empty default, use the $default_for_char one instead
504                         $default = "'" . $this->default_for_char . "'";
505                     } else {
506                         $default = "'" . $this->addslashes($xmldb_field->getDefault()) . "'";
507                     }
508             } else {
509                 $default = $xmldb_field->getDefault();
510             }
511         } else {
512         /// We force default '' for not null char columns without proper default
513         /// some day this should be out!
514             if ($this->default_for_char !== NULL &&
515                 $xmldb_field->getType() == XMLDB_TYPE_CHAR &&
516                 $xmldb_field->getNotNull()) {
517                 $default = "'" . $this->default_for_char . "'";
518             } else {
519             /// If the DB requires to explicity define some clause to drop one default, do it here
520             /// never applying defaults to TEXT and BINARY fields
521                 if ($this->drop_default_value_required &&
522                     $xmldb_field->getType() != XMLDB_TYPE_TEXT &&
523                     $xmldb_field->getType() != XMLDB_TYPE_BINARY && !$xmldb_field->getNotNull()) {
524                     $default = $this->drop_default_value;
525                 }
526             }
527         }
528         return $default;
529     }
531     /**
532      * Given one xmldb_field, returns the correct "default clause" for the current configuration
533      */
534     public function getDefaultClause($xmldb_field) {
536         $defaultvalue = $this->getDefaultValue ($xmldb_field);
538         if ($defaultvalue !== null) {
539             return ' DEFAULT ' . $defaultvalue;
540         } else {
541             return null;
542         }
543     }
545     /**
546      * Given one correct xmldb_table and the new name, returns the SQL statements
547      * to rename it (inside one array)
548      */
549     public function getRenameTableSQL($xmldb_table, $newname) {
551         $results = array();  //Array where all the sentences will be stored
553         $newt = new xmldb_table($newname); //Temporal table for name calculations
555         $rename = str_replace('OLDNAME', $this->getTableName($xmldb_table), $this->rename_table_sql);
556         $rename = str_replace('NEWNAME', $this->getTableName($newt), $rename);
558         $results[] = $rename;
560     /// Call to getRenameTableExtraSQL() override if needed
561         $extra_sentences = $this->getRenameTableExtraSQL($xmldb_table, $newname);
562         $results = array_merge($results, $extra_sentences);
564         return $results;
565     }
567     /**
568      * Given one correct xmldb_table and the new name, returns the SQL statements
569      * to drop it (inside one array)
570      */
571     public function getDropTableSQL($xmldb_table) {
573         $results = array();  //Array where all the sentences will be stored
575         $drop = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_table_sql);
577         $results[] = $drop;
579     /// call to getDropTableExtraSQL(), override if needed
580         $extra_sentences = $this->getDropTableExtraSQL($xmldb_table);
581         $results = array_merge($results, $extra_sentences);
583         return $results;
584     }
586     /**
587      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add the field to the table
588      */
589     public function getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
591         $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
592         $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
593         $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
595         $results = array();
597     /// Get the quoted name of the table and field
598         $tablename = $this->getTableName($xmldb_table);
600     /// Build the standard alter table add
601         $sql = $this->getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause,
602                                   $skip_default_clause,
603                                   $skip_notnull_clause);
604         $altertable = 'ALTER TABLE ' . $tablename . ' ADD ' . $sql;
605     /// Add the after clause if necesary
606         if ($this->add_after_clause && $xmldb_field->getPrevious()) {
607             $altertable .= ' AFTER ' . $this->getEncQuoted($xmldb_field->getPrevious());
608         }
609         $results[] = $altertable;
611         return $results;
612     }
614     /**
615      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop the field from the table
616      */
617     public function getDropFieldSQL($xmldb_table, $xmldb_field) {
619         $results = array();
621     /// Get the quoted name of the table and field
622         $tablename = $this->getTableName($xmldb_table);
623         $fieldname = $this->getEncQuoted($xmldb_field->getName());
625     /// Build the standard alter table drop
626         $results[] = 'ALTER TABLE ' . $tablename . ' DROP COLUMN ' . $fieldname;
628         return $results;
629     }
631     /**
632      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to alter the field in the table
633      */
634     public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
636         $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
637         $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
638         $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
640         $results = array();
642     /// Get the quoted name of the table and field
643         $tablename = $this->getTableName($xmldb_table);
644         $fieldname = $this->getEncQuoted($xmldb_field->getName());
646     /// Build de alter sentence using the alter_column_sql template
647         $alter = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->alter_column_sql);
648         $colspec = $this->getFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause,
649                                       $skip_default_clause,
650                                       $skip_notnull_clause,
651                                       true);
652         $alter = str_replace('COLUMNSPECS', $colspec, $alter);
654     /// Add the after clause if necesary
655         if ($this->add_after_clause && $xmldb_field->getPrevious()) {
656             $alter .= ' after ' . $this->getEncQuoted($xmldb_field->getPrevious());
657         }
659     /// Build the standard alter table modify
660         $results[] = $alter;
662         return $results;
663     }
665     /**
666      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to modify the default of the field in the table
667      */
668     public function getModifyDefaultSQL($xmldb_table, $xmldb_field) {
670         $results = array();
672     /// Get the quoted name of the table and field
673         $tablename = $this->getTableName($xmldb_table);
674         $fieldname = $this->getEncQuoted($xmldb_field->getName());
676     /// Decide if we are going to create/modify or to drop the default
677         if ($xmldb_field->getDefault() === null) {
678             $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop
679         } else {
680             $results = $this->getCreateDefaultSQL($xmldb_table, $xmldb_field); //Create/modify
681         }
683         return $results;
684     }
686     /**
687      * Given one correct xmldb_field and the new name, returns the SQL statements
688      * to rename it (inside one array)
689      */
690     public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
692         $results = array();  //Array where all the sentences will be stored
694     /// Although this is checked in database_manager::rename_field() - double check
695     /// that we aren't trying to rename one "id" field. Although it could be
696     /// implemented (if adding the necessary code to rename sequences, defaults,
697     /// triggers... and so on under each getRenameFieldExtraSQL() function, it's
698     /// better to forbid it, mainly because this field is the default PK and
699     /// in the future, a lot of FKs can be pointing here. So, this field, more
700     /// or less, must be considered immutable!
701         if ($xmldb_field->getName() == 'id') {
702             return array();
703         }
705         $rename = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_column_sql);
706         $rename = str_replace('OLDFIELDNAME', $this->getEncQuoted($xmldb_field->getName()), $rename);
707         $rename = str_replace('NEWFIELDNAME', $this->getEncQuoted($newname), $rename);
709         $results[] = $rename;
711     /// Call to getRenameFieldExtraSQL(), override if needed
712         $extra_sentences = $this->getRenameFieldExtraSQL($xmldb_table, $xmldb_field, $newname);
713         $results = array_merge($results, $extra_sentences);
715         return $results;
716     }
718     /**
719      * Given one xmldb_table and one xmldb_key, return the SQL statements needed to add the key to the table
720      * note that undelying indexes will be added as parametrised by $xxxx_keys and $xxxx_index parameters
721      */
722     public function getAddKeySQL($xmldb_table, $xmldb_key) {
724         $results = array();
726     /// Just use the CreateKeySQL function
727         if ($keyclause = $this->getKeySQL($xmldb_table, $xmldb_key)) {
728             $key = 'ALTER TABLE ' . $this->getTableName($xmldb_table) .
729                ' ADD CONSTRAINT ' . $keyclause;
730             $results[] = $key;
731         }
733     /// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
734     /// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
735         if (!$keyclause || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
736         /// Only if they don't exist
737             if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN) {  ///Calculate type of index based on type ok key
738                 $indextype = XMLDB_INDEX_NOTUNIQUE;
739             } else {
740                 $indextype = XMLDB_INDEX_UNIQUE;
741             }
742             $xmldb_index = new xmldb_index('anyname', $indextype, $xmldb_key->getFields());
743             if (!$this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {
744                 $results = array_merge($results, $this->getAddIndexSQL($xmldb_table, $xmldb_index));
745             }
746         }
748     /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
749         if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
750         ///Duplicate the key
751             $xmldb_key->setType(XMLDB_KEY_UNIQUE);
752             $results = array_merge($results, $this->getAddKeySQL($xmldb_table, $xmldb_key));
753         }
755     /// Return results
756         return $results;
757     }
759     /**
760      * Given one xmldb_table and one xmldb_index, return the SQL statements needed to drop the index from the table
761      */
762     public function getDropKeySQL($xmldb_table, $xmldb_key) {
764         $results = array();
766     /// Get the key name (note that this doesn't introspect DB, so could cause some problems sometimes!)
767     /// TODO: We'll need to overwrite the whole getDropKeySQL() method inside each DB to do the proper queries
768     /// against the dictionary or require ADOdb to support it or change the find_key_name() method to
769     /// perform DB introspection directly. But, for now, as we aren't going to enable referential integrity
770     /// it won't be a problem at all
771         $dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);
773     /// Only if such type of key generation is enabled
774         $dropkey = false;
775         switch ($xmldb_key->getType()) {
776             case XMLDB_KEY_PRIMARY:
777                 if ($this->primary_keys) {
778                     $template = $this->drop_primary_key;
779                     $dropkey = true;
780                 }
781                 break;
782             case XMLDB_KEY_UNIQUE:
783                 if ($this->unique_keys) {
784                     $template = $this->drop_unique_key;
785                     $dropkey = true;
786                 }
787                 break;
788             case XMLDB_KEY_FOREIGN_UNIQUE:
789             case XMLDB_KEY_FOREIGN:
790                 if ($this->foreign_keys) {
791                     $template = $this->drop_foreign_key;
792                     $dropkey = true;
793                 }
794                 break;
795         }
796     /// If we have decided to drop the key, let's do it
797         if ($dropkey) {
798         /// Replace TABLENAME, CONSTRAINTTYPE and KEYNAME as needed
799             $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $template);
800             $dropsql = str_replace('KEYNAME', $dbkeyname, $dropsql);
802             $results[] = $dropsql;
803         }
805     /// If we aren't dropping the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
806     /// automatically by the RDBMS) drop the underlying (created by us) index (if exists)
807         if (!$dropkey || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
808         /// Only if they exist
809             $xmldb_index = new xmldb_index('anyname', XMLDB_INDEX_UNIQUE, $xmldb_key->getFields());
810             if ($this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {
811                 $results = array_merge($results, $this->getDropIndexSQL($xmldb_table, $xmldb_index));
812             }
813         }
815     /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, drop the UNIQUE too
816         if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
817         ///Duplicate the key
818             $xmldb_key->setType(XMLDB_KEY_UNIQUE);
819             $results = array_merge($results, $this->getDropKeySQL($xmldb_table, $xmldb_key));
820         }
822     /// Return results
823         return $results;
824     }
826     /**
827      * Given one xmldb_table and one xmldb_key, return the SQL statements needed to rename the key in the table
828      * Experimental! Shouldn't be used at all!
829      */
831     public function getRenameKeySQL($xmldb_table, $xmldb_key, $newname) {
833         $results = array();
835     /// Get the real key name
836         $dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);
838     /// Check we are really generating this type of keys
839         if (($xmldb_key->getType() == XMLDB_KEY_PRIMARY && !$this->primary_keys) ||
840             ($xmldb_key->getType() == XMLDB_KEY_UNIQUE && !$this->unique_keys) ||
841             ($xmldb_key->getType() == XMLDB_KEY_FOREIGN && !$this->foreign_keys) ||
842             ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && !$this->unique_keys && !$this->foreign_keys)) {
843         /// We aren't generating this type of keys, delegate to child indexes
844             $xmldb_index = new xmldb_index($xmldb_key->getName());
845             $xmldb_index->setFields($xmldb_key->getFields());
846             return $this->getRenameIndexSQL($xmldb_table, $xmldb_index, $newname);
847         }
849     /// Arrived here so we are working with keys, lets rename them
850     /// Replace TABLENAME and KEYNAME as needed
851         $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_key_sql);
852         $renamesql = str_replace('OLDKEYNAME', $dbkeyname, $renamesql);
853         $renamesql = str_replace('NEWKEYNAME', $newname, $renamesql);
855     /// Some DB doesn't support key renaming so this can be empty
856         if ($renamesql) {
857             $results[] = $renamesql;
858         }
860         return $results;
861     }
863     /**
864      * Given one xmldb_table and one xmldb_index, return the SQL statements needed to add the index to the table
865      */
866     public function getAddIndexSQL($xmldb_table, $xmldb_index) {
868     /// Just use the CreateIndexSQL function
869         return $this->getCreateIndexSQL($xmldb_table, $xmldb_index);
870     }
872     /**
873      * Given one xmldb_table and one xmldb_index, return the SQL statements needed to drop the index from the table
874      */
875     public function getDropIndexSQL($xmldb_table, $xmldb_index) {
877         $results = array();
879     /// Get the real index name
880         $dbindexname = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index);
882     /// Replace TABLENAME and INDEXNAME as needed
883         $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_index_sql);
884         $dropsql = str_replace('INDEXNAME', $this->getEncQuoted($dbindexname), $dropsql);
886         $results[] = $dropsql;
888         return $results;
889     }
891     /**
892      * Given one xmldb_table and one xmldb_index, return the SQL statements needed to rename the index in the table
893      * Experimental! Shouldn't be used at all!
894      */
895     function getRenameIndexSQL($xmldb_table, $xmldb_index, $newname) {
896     /// Some DB doesn't support index renaming (MySQL) so this can be empty
897         if (empty($this->rename_index_sql)) {
898             return array();
899         }
901     /// Get the real index name
902         $dbindexname = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index);
903     /// Replace TABLENAME and INDEXNAME as needed
904         $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_index_sql);
905         $renamesql = str_replace('OLDINDEXNAME', $this->getEncQuoted($dbindexname), $renamesql);
906         $renamesql = str_replace('NEWINDEXNAME', $this->getEncQuoted($newname), $renamesql);
908         return array($renamesql);
909     }
911     /**
912      * Given three strings (table name, list of fields (comma separated) and suffix),
913      * create the proper object name quoting it if necessary.
914      *
915      * IMPORTANT: This function must be used to CALCULATE NAMES of objects TO BE CREATED,
916      *            NEVER TO GUESS NAMES of EXISTING objects!!!
917      */
918     public function getNameForObject($tablename, $fields, $suffix='') {
920         $name = '';
922     /// Implement one basic cache to avoid object name duplication
923     /// along all the request life, but never to return cached results
924     /// We need this because sql statements are created before executing
925     /// them, hence names doesn't exist "physically" yet in DB, so we need
926     /// to known which ones have been used
927         static $used_names = array();
929     /// Use standard naming. See http://docs.moodle.org/en/XMLDB_key_and_index_naming
930         $tablearr = explode ('_', $tablename);
931         foreach ($tablearr as $table) {
932             $name .= substr(trim($table),0,4);
933         }
934         $name .= '_';
935         $fieldsarr = explode (',', $fields);
936         foreach ($fieldsarr as $field) {
937             $name .= substr(trim($field),0,3);
938         }
939     /// Prepend the prefix
940         $name = $this->prefix . $name;
942         $name = substr(trim($name), 0, $this->names_max_length - 1 - strlen($suffix)); //Max names_max_length
944     /// Add the suffix
945         $namewithsuffix = $name;
946         if ($suffix) {
947             $namewithsuffix = $namewithsuffix . '_' . $suffix;
948         }
950     /// If the calculated name is in the cache, or if we detect it by introspecting the DB let's modify if
951         if (in_array($namewithsuffix, $used_names) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {
952             $counter = 2;
953         /// If have free space, we add 2
954             if (strlen($namewithsuffix) < $this->names_max_length) {
955                 $newname = $name . $counter;
956         /// Else replace the last char by 2
957             } else {
958                 $newname = substr($name, 0, strlen($name)-1) . $counter;
959             }
960             $newnamewithsuffix = $newname;
961             if ($suffix) {
962                 $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
963             }
964         /// Now iterate until not used name is found, incrementing the counter
965             while (in_array($newnamewithsuffix, $used_names) || $this->isNameInUse($newnamewithsuffix, $suffix, $tablename)) {
966                 $counter++;
967                 $newname = substr($name, 0, strlen($newname)-1) . $counter;
968                 $newnamewithsuffix = $newname;
969                 if ($suffix) {
970                     $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
971                 }
972             }
973             $namewithsuffix = $newnamewithsuffix;
974         }
976     /// Add the name to the cache
977         $used_names[] = $namewithsuffix;
979     /// Quote it if necessary (reserved words)
980         $namewithsuffix = $this->getEncQuoted($namewithsuffix);
982         return $namewithsuffix;
983     }
985     /**
986      * Given any string (or one array), enclose it by the proper quotes
987      * if it's a reserved word
988      */
989     public function getEncQuoted($input) {
991         if (is_array($input)) {
992             foreach ($input as $key=>$content) {
993                 $input[$key] = $this->getEncQuoted($content);
994             }
995             return $input;
996         } else {
997         /// Always lowercase
998             $input = strtolower($input);
999         /// if reserved or quote_all or has hyphens, quote it
1000             if ($this->quote_all || in_array($input, $this->reserved_words) || strpos($input, '-') !== false) {
1001                 $input = $this->quote_string . $input . $this->quote_string;
1002             }
1003             return $input;
1004         }
1005     }
1007     /**
1008      * Given one XMLDB Statement, build the needed SQL insert sentences to execute it
1009      */
1010     function getExecuteInsertSQL($statement) {
1012          $results = array();  //Array where all the sentences will be stored
1014          if ($sentences = $statement->getSentences()) {
1015              foreach ($sentences as $sentence) {
1016              /// Get the list of fields
1017                  $fields = $statement->getFieldsFromInsertSentence($sentence);
1018              /// Get the values of fields
1019                  $values = $statement->getValuesFromInsertSentence($sentence);
1020              /// Look if we have some CONCAT value and transform it dynamically
1021                  foreach($values as $key => $value) {
1022                  /// Trim single quotes
1023                      $value = trim($value,"'");
1024                      if (stristr($value, 'CONCAT') !== false){
1025                      /// Look for data between parenthesis
1026                          preg_match("/CONCAT\s*\((.*)\)$/is", trim($value), $matches);
1027                          if (isset($matches[1])) {
1028                              $part = $matches[1];
1029                          /// Convert the comma separated string to an array
1030                              $arr = xmldb_object::comma2array($part);
1031                              if ($arr) {
1032                                  $value = $this->getConcatSQL($arr);
1033                              }
1034                          }
1035                      }
1036                  /// Values to be sent to DB must be properly escaped
1037                      $value = $this->addslashes($value);
1038                  /// Back trimmed quotes
1039                      $value = "'" . $value . "'";
1040                  /// Back to the array
1041                      $values[$key] = $value;
1042                  }
1044              /// Iterate over fields, escaping them if necessary
1045                  foreach($fields as $key => $field) {
1046                      $fields[$key] = $this->getEncQuoted($field);
1047                  }
1048              /// Build the final SQL sentence and add it to the array of results
1049              $sql = 'INSERT INTO ' . $this->getEncQuoted($this->prefix . $statement->getTable()) .
1050                          '(' . implode(', ', $fields) . ') ' .
1051                          'VALUES (' . implode(', ', $values) . ')';
1052                  $results[] = $sql;
1053              }
1055          }
1056          return $results;
1057     }
1059     /**
1060      * Given one array of elements, build de proper CONCAT expression, based
1061      * in the $concat_character setting. If such setting is empty, then
1062      * MySQL's CONCAT function will be used instead
1063      */
1064     public function getConcatSQL($elements) {
1066     /// Replace double quoted elements by single quotes
1067         foreach($elements as $key => $element) {
1068             $element = trim($element);
1069             if (substr($element, 0, 1) == '"' &&
1070                 substr($element, -1, 1) == '"') {
1071                     $elements[$key] = "'" . trim($element, '"') . "'";
1072             }
1073         }
1075     /// Now call the standard $DB->sql_concat() DML function
1076         return call_user_func_array(array($this->mdb, 'sql_concat'), $elements);
1077     }
1079     /**
1080      * Returns the name (string) of the sequence used in the table for the autonumeric pk
1081      * Only some DB have this implemented
1082      */
1083     public function getSequenceFromDB($xmldb_table) {
1084         return false;
1085     }
1087     /**
1088      * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg)
1089      * return if such name is currently in use (true) or no (false)
1090      * (MySQL requires the whole xmldb_table object to be specified, so we add it always)
1091      * (invoked from getNameForObject()
1092      * Only some DB have this implemented
1093      */
1094     public function isNameInUse($object_name, $type, $table_name) {
1095         return false; //For generators not implementing introspection,
1096                       //we always return with the name being free to be used
1097     }
1100 /// ALL THESE FUNCTION MUST BE CUSTOMISED BY ALL THE XMLDGenerator classes
1102     /**
1103      * Reset a sequence to the id field of a table.
1104      * @param string $table name of table
1105      * @return success
1106      */
1107     public abstract function getResetSequenceSQL($tablename);
1109     /**
1110      * Given one correct xmldb_table, returns the SQL statements
1111      * to create temporary table (inside one array)
1112      */
1113     abstract public function getCreateTempTableSQL($xmldb_table);
1115     /**
1116      * Given one correct xmldb_table and the new name, returns the SQL statements
1117      * to drop it (inside one array)
1118      */
1119     abstract public function getDropTempTableSQL($xmldb_table);
1121     /**
1122      * Given one XMLDB Type, length and decimals, returns the DB proper SQL type
1123      */
1124     public abstract function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null);
1126     /**
1127      * Returns the code (array of statements) needed to execute extra statements on field rename
1128      */
1129     public function getRenameFieldExtraSQL($xmldb_table, $xmldb_field) {
1130         return array();
1131     }
1133     /**
1134      * Returns the code (array of statements) needed
1135      * to create one sequence for the xmldb_table and xmldb_field passes
1136      */
1137     public function getCreateSequenceSQL($xmldb_table, $xmldb_field) {
1138         return array();
1139     }
1141     /**
1142      * Returns the code (array of statements) needed to add one comment to the table
1143      */
1144     public abstract function getCommentSQL($xmldb_table);
1146     /**
1147      * Returns the code (array of statements) needed to execute extra statements on table rename
1148      */
1149     public function getRenameTableExtraSQL($xmldb_table, $newname) {
1150         return array();
1151     }
1153     /**
1154      * Returns the code (array of statements) needed to execute extra statements on table drop
1155      */
1156     public function getDropTableExtraSQL($xmldb_table) {
1157         return array();
1158     }
1160     /**
1161      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its enum
1162      * (usually invoked from getModifyEnumSQL()
1163      *
1164      * TODO: Moodle 2.1 - Drop getDropEnumSQL()
1165      */
1166     public abstract function getDropEnumSQL($xmldb_table, $xmldb_field);
1168     /**
1169      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default
1170      * (usually invoked from getModifyDefaultSQL()
1171      *
1172      * TODO: Moodle 2.1 - Drop getDropDefaultSQL()
1173      */
1174     public abstract function getDropDefaultSQL($xmldb_table, $xmldb_field);
1176     /**
1177      * Given one xmldb_table and one optional xmldb_field, return one array with all the check
1178      * constrainst found for that table (or field). Must exist for each DB supported.
1179      * (usually invoked from find_check_constraint_name)
1180      *
1181      * TODO: Moodle 2.1 - Drop getCheckConstraintsFromDB
1182      */
1183     public abstract function getCheckConstraintsFromDB($xmldb_table, $xmldb_field=null);
1185     /**
1186      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to add its default
1187      * (usually invoked from getModifyDefaultSQL()
1188      */
1189     public abstract function getCreateDefaultSQL($xmldb_table, $xmldb_field);
1191     /**
1192      * Returns an array of reserved words (lowercase) for this DB
1193      * You MUST provide the real list for each DB inside every XMLDB class
1194      * @return array of reserved words
1195      */
1196     public static function getReservedWords() {
1197         throw new coding_exception('getReservedWords() method needs to be overridden in each subclass of sql_generator');
1198     }
1200     /**
1201      * Returns all reserved works in supported databases.
1202      * Reserved words should be lowercase.
1203      * @return array ('word'=>array(databases))
1204      */
1205     public static function getAllReservedWords() {
1206         global $CFG;
1208         $generators = array('mysql', 'postgres', 'oracle', 'mssql');
1209         $reserved_words = array();
1211         foreach($generators as $generator) {
1212             $class = $generator . '_sql_generator';
1213             require_once("$CFG->libdir/ddl/$class.php");
1214             foreach (call_user_func(array($class, 'getReservedWords')) as $word) {
1215                 $reserved_words[$word][] = $generator;
1216             }
1217         }
1218         ksort($reserved_words);
1219         return $reserved_words;
1220     }
1222     public function addslashes($s) {
1223         // do not use php addslashes() because it depends on PHP quote settings!
1224         $s = str_replace('\\','\\\\',$s);
1225         $s = str_replace("\0","\\\0", $s);
1226         $s = str_replace("'",  "\\'", $s);
1227         return $s;
1228     }