MDL-21874 - phisics are physics :-P
[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 overriden as needed to generate correct SQL.
25  *
26  * @package    moodlecore
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 /**
34  * Abstract sql generator class, base for all bd specific implementations.
35  */
36 abstract class sql_generator {
38 /// Please, avoid editing this defaults in this base class!
39 /// It could change the behaviour of the rest of generators
40 /// that, by default, inherit this configuration.
41 /// To change any of them, do it in extended classes instead.
43     public $quote_string = '"';   // String used to quote names
45     public $statement_end = ';'; // String to be automatically added at the end of each statement
47     public $quote_all    = false; // To decide if we want to quote all the names or only the reserved ones
49     public $integer_to_number = false;  // To create all the integers as NUMBER(x) (also called DECIMAL, NUMERIC...)
50     public $float_to_number   = false;  // To create all the floats as NUMBER(x) (also called DECIMAL, NUMERIC...)
52     public $number_type = 'NUMERIC';    // Proper type for NUMBER(x) in this DB
54     public $unsigned_allowed = true;    // To define in the generator must handle unsigned information
55     public $default_for_char = null;      // To define the default to set for NOT NULLs CHARs without default (null=do nothing)
57     public $drop_default_value_required = false; //To specify if the generator must use some DEFAULT clause to drop defaults
58     public $drop_default_value = ''; //The DEFAULT clause required to drop defaults
60     public $default_after_null = true;  //To decide if the default clause of each field must go after the null clause
62     public $specify_nulls = false;  //To force the generator if NULL clauses must be specified. It shouldn't be necessary
63                                  //but some mssql drivers require them or everything is created as NOT NULL :-(
65     public $primary_key_name = null; //To force primary key names to one string (null=no force)
67     public $primary_keys = true;  // Does the generator build primary keys
68     public $unique_keys = false;  // Does the generator build unique keys
69     public $foreign_keys = false; // Does the generator build foreign keys
71     public $drop_primary_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop PKs
72                                // with automatic replace for TABLENAME and KEYNAME
74     public $drop_unique_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop UKs
75                                // with automatic replace for TABLENAME and KEYNAME
77     public $drop_foreign_key = 'ALTER TABLE TABLENAME DROP CONSTRAINT KEYNAME'; // Template to drop FKs
78                                // with automatic replace for TABLENAME and KEYNAME
80     public $sequence_extra_code = true; //Does the generator need to add extra code to generate the sequence fields
81     public $sequence_name = 'auto_increment'; //Particular name for inline sequences in this generator
82     public $sequence_name_small = false; //Different name for small (4byte) sequences or false if same
83     public $sequence_only = false; //To avoid to output the rest of the field specs, leaving only the name and the sequence_name publiciable
85     public $add_table_comments  = true;  // Does the generator need to add code for table comments
87     public $add_after_clause = false; // Does the generator need to add the after clause for fields
89     public $prefix_on_names = true; //Does the generator need to prepend the prefix to all the key/index/sequence/trigger/check names
91     public $names_max_length = 30; //Max length for key/index/sequence/trigger/check names (keep 30 for all!)
93     public $concat_character = '||'; //Characters to be used as concatenation operator. If not defined
94                                   //MySQL CONCAT function will be used
96     public $rename_table_sql = 'ALTER TABLE OLDNAME RENAME TO NEWNAME'; //SQL sentence to rename one table, both
97                                   //OLDNAME and NEWNAME are dinamically replaced
99     public $drop_table_sql = 'DROP TABLE TABLENAME'; //SQL sentence to drop one table
100                                   //TABLENAME is dinamically replaced
102     public $alter_column_sql = 'ALTER TABLE TABLENAME ALTER COLUMN COLUMNSPECS'; //The SQL template to alter columns
104     public $alter_column_skip_default = false; //The generator will skip the default clause on alter columns
106     public $alter_column_skip_type = false; //The generator will skip the type clause on alter columns
108     public $alter_column_skip_notnull = false; //The generator will skip the null/notnull clause on alter columns
110     public $rename_column_sql = 'ALTER TABLE TABLENAME RENAME COLUMN OLDFIELDNAME TO NEWFIELDNAME';
111                                   ///TABLENAME, OLDFIELDNAME and NEWFIELDNAME are dianmically replaced
113     public $drop_index_sql = 'DROP INDEX INDEXNAME'; //SQL sentence to drop one index
114                                   //TABLENAME, INDEXNAME are dinamically replaced
116     public $rename_index_sql = 'ALTER INDEX OLDINDEXNAME RENAME TO NEWINDEXNAME'; //SQL sentence to rename one index
117                                   //TABLENAME, OLDINDEXNAME, NEWINDEXNAME are dinamically replaced
119     public $rename_key_sql = 'ALTER TABLE TABLENAME CONSTRAINT OLDKEYNAME RENAME TO NEWKEYNAME'; //SQL sentence to rename one key
120                                   //TABLENAME, OLDKEYNAME, NEWKEYNAME are dinamically replaced
122     public $prefix;         // Prefix to be used for all the DB objects
124     public $reserved_words; // List of reserved words (in order to quote them properly)
126     public $mdb;
128     /**
129      * Creates new sql_generator
130      * @param object moodle_database instance
131      */
132     public function __construct($mdb) {
133         $this->prefix         = $mdb->get_prefix();
134         $this->reserved_words = $this->getReservedWords();
135         $this->mdb            = $mdb; // this creates circular reference - the other link must be unset when closing db
136     }
138     /**
139      * Release all resources
140      */
141     public function dispose() {
142         $this->mdb = null;
143     }
145     /**
146      * Given one string (or one array), ends it with statement_end
147      */
148     public function getEndedStatements($input) {
150         if (is_array($input)) {
151             foreach ($input as $key=>$content) {
152                 $input[$key] = $this->getEndedStatements($content);
153             }
154             return $input;
155         } else {
156             $input = trim($input).$this->statement_end;
157             return $input;
158         }
159     }
161     /**
162      * Given one xmldb_table, check if it exists in DB (true/false)
163      *
164      * @param mixed the table to be searched (string name or xmldb_table instance)
165      * @return boolean true/false
166      */
167     public function table_exists($table) {
168         if (is_string($table)) {
169             $tablename = $table;
170         } else {
171         /// Calculate the name of the table
172             $tablename = $table->getName();
173         }
175     /// get all tables in moodle database
176         $tables = $this->mdb->get_tables();
177         $exists = in_array($tablename, $tables);
179         return $exists;
180     }
182     /**
183      * Reset a sequence to the id field of a table.
184      * @param string $table name of table
185      * @return success
186      */
187     public abstract function getResetSequenceSQL($tablename);
189     /**
190      * This function will return the SQL code needed to create db tables and statements
191      */
192     public function getCreateStructureSQL($xmldb_structure) {
193         $results = array();
195         if ($tables = $xmldb_structure->getTables()) {
196             foreach ($tables as $table) {
197                 $results = array_merge($results, $this->getCreateTableSQL($table));
198             }
199         }
201         if ($statements = $xmldb_structure->getStatements()) {
202             foreach ($statements as $statement) {
203                 $results = array_merge($results, $this->getExecuteStatementSQL($statement));
204             }
205         }
206         return $results;
207     }
209     /**
210      * This function will return the code needed to execute a collection
211      * of sentences present inside one statement for the specified BD
212      * and prefix.
213      * For now it only supports INSERT statements
214      */
215     public function getExecuteStatementSQL($xmldb_statement) {
217         $results = array();
219     /// Based on statement type
220         switch ($xmldb_statement->type) {
221             case XMLDB_STATEMENT_INSERT:
222                 $results = $this->getExecuteInsertSQL($xmldb_statement);
223                 break;
224             case XMLDB_STATEMENT_UPDATE:
225                 break;
226             case XMLDB_STATEMENT_DELETE:
227                 break;
228             case XMLDB_STATEMENT_CUSTOM:
229                 break;
230         }
232         return $results;
233     }
235     /**
236      * Given one xmldb_table, returns it's correct name, depending of all the parametrization
237      *
238      * @param xmldb_table table whose name we want
239      * @param boolean to specify if the name must be quoted (if reserved word, only!)
240      * @return string the correct name of the table
241      */
242     public function getTableName(xmldb_table $xmldb_table, $quoted=true) {
243     /// Get the name
244         $tablename = $this->prefix.$xmldb_table->getName();
246     /// Apply quotes optionally
247         if ($quoted) {
248             $tablename = $this->getEncQuoted($tablename);
249         }
251         return $tablename;
252     }
254     /**
255      * Given one correct xmldb_table, returns the SQL statements
256      * to create it (inside one array)
257      */
258     public function getCreateTableSQL($xmldb_table) {
260         $results = array();  //Array where all the sentences will be stored
262     /// Table header
263         $table = 'CREATE TABLE ' . $this->getTableName($xmldb_table) . ' (';
265         if (!$xmldb_fields = $xmldb_table->getFields()) {
266             return $results;
267         }
269     /// Add the fields, separated by commas
270         foreach ($xmldb_fields as $xmldb_field) {
271             $table .= "\n    " . $this->getFieldSQL($xmldb_field);
272             $table .= ',';
273         }
274     /// Add the keys, separated by commas
275         if ($xmldb_keys = $xmldb_table->getKeys()) {
276             foreach ($xmldb_keys as $xmldb_key) {
277                 if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
278                     $table .= "\nCONSTRAINT " . $keytext . ',';
279                 }
280             /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
281                 if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE) {
282                 ///Duplicate the key
283                     $xmldb_key->setType(XMLDB_KEY_UNIQUE);
284                     if ($keytext = $this->getKeySQL($xmldb_table, $xmldb_key)) {
285                         $table .= "\nCONSTRAINT " . $keytext . ',';
286                     }
287                 }
288             }
289         }
290     /// Table footer, trim the latest comma
291         $table = trim($table,',');
292         $table .= "\n)";
294     /// Add the CREATE TABLE to results
295         $results[] = $table;
297     /// Add comments if specified and it exists
298         if ($this->add_table_comments && $xmldb_table->getComment()) {
299             $comment = $this->getCommentSQL($xmldb_table);
300         /// Add the COMMENT to results
301             $results = array_merge($results, $comment);
302         }
304     /// Add the indexes (each one, one statement)
305         if ($xmldb_indexes = $xmldb_table->getIndexes()) {
306             foreach ($xmldb_indexes as $xmldb_index) {
307             ///tables do not exist yet, which means indexed can not exist yet
308                 if ($indextext = $this->getCreateIndexSQL($xmldb_table, $xmldb_index)) {
309                     $results = array_merge($results, $indextext);
310                 }
311             }
312         }
314     /// Also, add the indexes needed from keys, based on configuration (each one, one statement)
315         if ($xmldb_keys = $xmldb_table->getKeys()) {
316             foreach ($xmldb_keys as $xmldb_key) {
317             /// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
318             /// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
319                 if (!$this->getKeySQL($xmldb_table, $xmldb_key) || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
320                 /// Create the interim index
321                     $index = new xmldb_index('anyname');
322                     $index->setFields($xmldb_key->getFields());
323                 ///tables do not exist yet, which means indexed can not exist yet
324                     $createindex = false; //By default
325                     switch ($xmldb_key->getType()) {
326                         case XMLDB_KEY_UNIQUE:
327                         case XMLDB_KEY_FOREIGN_UNIQUE:
328                             $index->setUnique(true);
329                             $createindex = true;
330                             break;
331                         case XMLDB_KEY_FOREIGN:
332                             $index->setUnique(false);
333                             $createindex = true;
334                             break;
335                     }
336                     if ($createindex) {
337                         if ($indextext = $this->getCreateIndexSQL($xmldb_table, $index)) {
338                         /// Add the INDEX to the array
339                             $results = array_merge($results, $indextext);
340                         }
341                     }
342                 }
343             }
344         }
346     /// Add sequence extra code if needed
347         if ($this->sequence_extra_code) {
348         /// Iterate over fields looking for sequences
349             foreach ($xmldb_fields as $xmldb_field) {
350                 if ($xmldb_field->getSequence()) {
351                 /// returns an array of statements needed to create one sequence
352                     $sequence_sentences = $this->getCreateSequenceSQL($xmldb_table, $xmldb_field);
353                 /// Add the SEQUENCE to the array
354                     $results = array_merge($results, $sequence_sentences);
355                 }
356             }
357         }
359         return $results;
360     }
362     /**
363      * Given one correct xmldb_table, returns the SQL statements
364      * to create temporary table (inside one array)
365      */
366     public function getCreateTempTableSQL($xmldb_table) {
367         $sqlarr = $this->getCreateTableSQL($xmldb_table);
368         $sqlarr = preg_replace('/^CREATE TABLE/', "CREATE TEMPORARY TABLE", $sqlarr);
369         return $sqlarr;
370     }
372     /**
373      * Given one correct xmldb_index, returns the SQL statements
374      * needed to create it (in array)
375      */
376     public function getCreateIndexSQL($xmldb_table, $xmldb_index) {
378         $unique = '';
379         $suffix = 'ix';
380         if ($xmldb_index->getUnique()) {
381             $unique = ' UNIQUE';
382             $suffix = 'uix';
383         }
385         $index = 'CREATE' . $unique . ' INDEX ';
386         $index .= $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_index->getFields()), $suffix);
387         $index .= ' ON ' . $this->getTableName($xmldb_table);
388         $index .= ' (' . implode(', ', $this->getEncQuoted($xmldb_index->getFields())) . ')';
390         return array($index);
391     }
393     /**
394      * Given one correct xmldb_field, returns the complete SQL line to create it
395      */
396     public function getFieldSQL($xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL, $specify_nulls_clause = NULL, $specify_field_name = true)  {
398         $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
399         $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
400         $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
401         $specify_nulls_clause = is_null($specify_nulls_clause) ? $this->specify_nulls : $specify_nulls_clause;
403     /// First of all, convert integers to numbers if defined
404         if ($this->integer_to_number) {
405             if ($xmldb_field->getType() == XMLDB_TYPE_INTEGER) {
406                 $xmldb_field->setType(XMLDB_TYPE_NUMBER);
407             }
408         }
409     /// Same for floats
410         if ($this->float_to_number) {
411             if ($xmldb_field->getType() == XMLDB_TYPE_FLOAT) {
412                 $xmldb_field->setType(XMLDB_TYPE_NUMBER);
413             }
414         }
416         $field = ''; // Let's accumulate the whole expression based on params and settings
417     /// The name
418         if ($specify_field_name) {
419             $field .= $this->getEncQuoted($xmldb_field->getName());
420         }
421     /// The type and length only if we don't want to skip it
422         if (!$skip_type_clause) {
423         /// The type and length
424             $field .= ' ' . $this->getTypeSQL($xmldb_field->getType(), $xmldb_field->getLength(), $xmldb_field->getDecimals());
425         }
426     /// The unsigned if supported
427         if ($this->unsigned_allowed && ($xmldb_field->getType() == XMLDB_TYPE_INTEGER ||
428                                       $xmldb_field->getType() == XMLDB_TYPE_NUMBER ||
429                                       $xmldb_field->getType() == XMLDB_TYPE_FLOAT)) {
430             if ($xmldb_field->getUnsigned()) {
431                 $field .= ' unsigned';
432             }
433         }
434     /// Calculate the not null clause
435         $notnull = '';
436     /// Only if we don't want to skip it
437         if (!$skip_notnull_clause) {
438             if ($xmldb_field->getNotNull()) {
439                 $notnull = ' NOT NULL';
440             } else {
441                 if ($specify_nulls_clause) {
442                     $notnull = ' NULL';
443                 }
444             }
445         }
446     /// Calculate the default clause
447         $default_clause = '';
448         if (!$skip_default_clause) { //Only if we don't want to skip it
449             $default_clause = $this->getDefaultClause($xmldb_field);
450         }
451     /// Based on default_after_null, set both clauses properly
452         if ($this->default_after_null) {
453             $field .= $notnull . $default_clause;
454         } else {
455             $field .= $default_clause . $notnull;
456         }
457     /// The sequence
458         if ($xmldb_field->getSequence()) {
459             if($xmldb_field->getLength()<=9 && $this->sequence_name_small) {
460                 $sequencename=$this->sequence_name_small;
461             } else {
462                 $sequencename=$this->sequence_name;
463             }
464             $field .= ' ' . $sequencename;
465             if ($this->sequence_only) {
466             /// We only want the field name and sequence name to be printed
467             /// so, calculate it and return
468                 $sql = $this->getEncQuoted($xmldb_field->getName()) . ' ' . $sequencename;
469                 return $sql;
470             }
471         }
472         return $field;
473     }
475     /**
476      * Given one correct xmldb_key, returns its specs
477      */
478     public function getKeySQL($xmldb_table, $xmldb_key) {
480         $key = '';
482         switch ($xmldb_key->getType()) {
483             case XMLDB_KEY_PRIMARY:
484                 if ($this->primary_keys) {
485                     if ($this->primary_key_name !== null) {
486                         $key = $this->getEncQuoted($this->primary_key_name);
487                     } else {
488                         $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'pk');
489                     }
490                     $key .= ' PRIMARY KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
491                 }
492                 break;
493             case XMLDB_KEY_UNIQUE:
494                 if ($this->unique_keys) {
495                     $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'uk');
496                     $key .= ' UNIQUE (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
497                 }
498                 break;
499             case XMLDB_KEY_FOREIGN:
500             case XMLDB_KEY_FOREIGN_UNIQUE:
501                 if ($this->foreign_keys) {
502                     $key = $this->getNameForObject($xmldb_table->getName(), implode(', ', $xmldb_key->getFields()), 'fk');
503                     $key .= ' FOREIGN KEY (' . implode(', ', $this->getEncQuoted($xmldb_key->getFields())) . ')';
504                     $key .= ' REFERENCES ' . $this->getEncQuoted($this->prefix . $xmldb_key->getRefTable());
505                     $key .= ' (' . implode(', ', $this->getEncQuoted($xmldb_key->getRefFields())) . ')';
506                 }
507                 break;
508         }
510         return $key;
511     }
513     /**
514      * Give one xmldb_field, returns the correct "default value" for the current configuration
515      */
516     public function getDefaultValue($xmldb_field) {
518         $default = null;
520         if ($xmldb_field->getDefault() !== NULL) {
521             if ($xmldb_field->getType() == XMLDB_TYPE_CHAR ||
522                 $xmldb_field->getType() == XMLDB_TYPE_TEXT) {
523                     if ($xmldb_field->getDefault() === '') { // If passing empty default, use the $default_for_char one instead
524                         $default = "'" . $this->default_for_char . "'";
525                     } else {
526                         $default = "'" . $this->addslashes($xmldb_field->getDefault()) . "'";
527                     }
528             } else {
529                 $default = $xmldb_field->getDefault();
530             }
531         } else {
532         /// We force default '' for not null char columns without proper default
533         /// some day this should be out!
534             if ($this->default_for_char !== NULL &&
535                 $xmldb_field->getType() == XMLDB_TYPE_CHAR &&
536                 $xmldb_field->getNotNull()) {
537                 $default = "'" . $this->default_for_char . "'";
538             } else {
539             /// If the DB requires to explicity define some clause to drop one default, do it here
540             /// never applying defaults to TEXT and BINARY fields
541                 if ($this->drop_default_value_required &&
542                     $xmldb_field->getType() != XMLDB_TYPE_TEXT &&
543                     $xmldb_field->getType() != XMLDB_TYPE_BINARY && !$xmldb_field->getNotNull()) {
544                     $default = $this->drop_default_value;
545                 }
546             }
547         }
548         return $default;
549     }
551     /**
552      * Given one xmldb_field, returns the correct "default clause" for the current configuration
553      */
554     public function getDefaultClause($xmldb_field) {
556         $defaultvalue = $this->getDefaultValue ($xmldb_field);
558         if ($defaultvalue !== null) {
559             return ' DEFAULT ' . $defaultvalue;
560         } else {
561             return null;
562         }
563     }
565     /**
566      * Given one correct xmldb_table and the new name, returns the SQL statements
567      * to rename it (inside one array)
568      */
569     public function getRenameTableSQL($xmldb_table, $newname) {
571         $results = array();  //Array where all the sentences will be stored
573         $newt = new xmldb_table($newname); //Temporal table for name calculations
575         $rename = str_replace('OLDNAME', $this->getTableName($xmldb_table), $this->rename_table_sql);
576         $rename = str_replace('NEWNAME', $this->getTableName($newt), $rename);
578         $results[] = $rename;
580     /// Call to getRenameTableExtraSQL() override if needed
581         $extra_sentences = $this->getRenameTableExtraSQL($xmldb_table, $newname);
582         $results = array_merge($results, $extra_sentences);
584         return $results;
585     }
587     /**
588      * Given one correct xmldb_table and the new name, returns the SQL statements
589      * to drop it (inside one array)
590      */
591     public function getDropTableSQL($xmldb_table) {
593         $results = array();  //Array where all the sentences will be stored
595         $drop = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_table_sql);
597         $results[] = $drop;
599     /// call to getDropTableExtraSQL(), override if needed
600         $extra_sentences = $this->getDropTableExtraSQL($xmldb_table);
601         $results = array_merge($results, $extra_sentences);
603         return $results;
604     }
606     /**
607      * Given one correct xmldb_table and the new name, returns the SQL statements
608      * to drop it (inside one array)
609      */
610     public function getDropTempTableSQL($xmldb_table) {
611         return $this->getDropTableSQL($xmldb_table);
612     }
614     /**
615      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to add the field to the table
616      */
617     public function getAddFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
619         $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
620         $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
621         $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
623         $results = array();
625     /// Get the quoted name of the table and field
626         $tablename = $this->getTableName($xmldb_table);
628     /// Build the standard alter table add
629         $sql = $this->getFieldSQL($xmldb_field, $skip_type_clause,
630                                   $skip_default_clause,
631                                   $skip_notnull_clause);
632         $altertable = 'ALTER TABLE ' . $tablename . ' ADD ' . $sql;
633     /// Add the after clause if necesary
634         if ($this->add_after_clause && $xmldb_field->getPrevious()) {
635             $altertable .= ' AFTER ' . $this->getEncQuoted($xmldb_field->getPrevious());
636         }
637         $results[] = $altertable;
639         return $results;
640     }
642     /**
643      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to drop the field from the table
644      */
645     public function getDropFieldSQL($xmldb_table, $xmldb_field) {
647         $results = array();
649     /// Get the quoted name of the table and field
650         $tablename = $this->getTableName($xmldb_table);
651         $fieldname = $this->getEncQuoted($xmldb_field->getName());
653     /// Build the standard alter table drop
654         $results[] = 'ALTER TABLE ' . $tablename . ' DROP COLUMN ' . $fieldname;
656         return $results;
657     }
659     /**
660      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to alter the field in the table
661      */
662     public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
664         $skip_type_clause = is_null($skip_type_clause) ? $this->alter_column_skip_type : $skip_type_clause;
665         $skip_default_clause = is_null($skip_default_clause) ? $this->alter_column_skip_default : $skip_default_clause;
666         $skip_notnull_clause = is_null($skip_notnull_clause) ? $this->alter_column_skip_notnull : $skip_notnull_clause;
668         $results = array();
670     /// Get the quoted name of the table and field
671         $tablename = $this->getTableName($xmldb_table);
672         $fieldname = $this->getEncQuoted($xmldb_field->getName());
674     /// Build de alter sentence using the alter_column_sql template
675         $alter = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->alter_column_sql);
676         $colspec = $this->getFieldSQL($xmldb_field, $skip_type_clause,
677                                       $skip_default_clause,
678                                       $skip_notnull_clause,
679                                       true);
680         $alter = str_replace('COLUMNSPECS', $colspec, $alter);
682     /// Add the after clause if necesary
683         if ($this->add_after_clause && $xmldb_field->getPrevious()) {
684             $alter .= ' after ' . $this->getEncQuoted($xmldb_field->getPrevious());
685         }
687     /// Build the standard alter table modify
688         $results[] = $alter;
690         return $results;
691     }
693     /**
694      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to modify the default of the field in the table
695      */
696     public function getModifyDefaultSQL($xmldb_table, $xmldb_field) {
698         $results = array();
700     /// Get the quoted name of the table and field
701         $tablename = $this->getTableName($xmldb_table);
702         $fieldname = $this->getEncQuoted($xmldb_field->getName());
704     /// Decide if we are going to create/modify or to drop the default
705         if ($xmldb_field->getDefault() === null) {
706             $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop
707         } else {
708             $results = $this->getCreateDefaultSQL($xmldb_table, $xmldb_field); //Create/modify
709         }
711         return $results;
712     }
714     /**
715      * Given one correct xmldb_field and the new name, returns the SQL statements
716      * to rename it (inside one array)
717      */
718     public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
720         $results = array();  //Array where all the sentences will be stored
722     /// Although this is checked in database_manager::rename_field() - double check
723     /// that we aren't trying to rename one "id" field. Although it could be
724     /// implemented (if adding the necessary code to rename sequences, defaults,
725     /// triggers... and so on under each getRenameFieldExtraSQL() function, it's
726     /// better to forbide it, mainly because this field is the default PK and
727     /// in the future, a lot of FKs can be pointing here. So, this field, more
728     /// or less, must be considered inmutable!
729         if ($xmldb_field->getName() == 'id') {
730             return array();
731         }
733         $rename = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_column_sql);
734         $rename = str_replace('OLDFIELDNAME', $this->getEncQuoted($xmldb_field->getName()), $rename);
735         $rename = str_replace('NEWFIELDNAME', $this->getEncQuoted($newname), $rename);
737         $results[] = $rename;
739     /// Call to getRenameFieldExtraSQL(), override if needed
740         $extra_sentences = $this->getRenameFieldExtraSQL($xmldb_table, $xmldb_field, $newname);
741         $results = array_merge($results, $extra_sentences);
743         return $results;
744     }
746     /**
747      * Given one xmldb_table and one xmldb_key, return the SQL statements needded to add the key to the table
748      * note that undelying indexes will be added as parametrised by $xxxx_keys and $xxxx_index parameters
749      */
750     public function getAddKeySQL($xmldb_table, $xmldb_key) {
752         $results = array();
754     /// Just use the CreateKeySQL function
755         if ($keyclause = $this->getKeySQL($xmldb_table, $xmldb_key)) {
756             $key = 'ALTER TABLE ' . $this->getTableName($xmldb_table) .
757                ' ADD CONSTRAINT ' . $keyclause;
758             $results[] = $key;
759         }
761     /// If we aren't creating the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
762     /// automatically by the RDBMS) create the underlying (created by us) index (if doesn't exists)
763         if (!$keyclause || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
764         /// Only if they don't exist
765             if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN) {  ///Calculate type of index based on type ok key
766                 $indextype = XMLDB_INDEX_NOTUNIQUE;
767             } else {
768                 $indextype = XMLDB_INDEX_UNIQUE;
769             }
770             $xmldb_index = new xmldb_index('anyname', $indextype, $xmldb_key->getFields());
771             if (!$this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {
772                 $results = array_merge($results, $this->getAddIndexSQL($xmldb_table, $xmldb_index));
773             }
774         }
776     /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, create it as UNIQUE too
777         if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
778         ///Duplicate the key
779             $xmldb_key->setType(XMLDB_KEY_UNIQUE);
780             $results = array_merge($results, $this->getAddKeySQL($xmldb_table, $xmldb_key));
781         }
783     /// Return results
784         return $results;
785     }
787     /**
788      * Given one xmldb_table and one xmldb_index, return the SQL statements needded to drop the index from the table
789      */
790     public function getDropKeySQL($xmldb_table, $xmldb_key) {
792         $results = array();
794     /// Get the key name (note that this doesn't introspect DB, so could cause some problems sometimes!)
795     /// TODO: We'll need to overwrite the whole getDropKeySQL() method inside each DB to do the proper queries
796     /// against the dictionary or require ADOdb to support it or change the find_key_name() method to
797     /// perform DB introspection directly. But, for now, as we aren't going to enable referential integrity
798     /// it won't be a problem at all
799         $dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);
801     /// Only if such type of key generation is enabled
802         $dropkey = false;
803         switch ($xmldb_key->getType()) {
804             case XMLDB_KEY_PRIMARY:
805                 if ($this->primary_keys) {
806                     $template = $this->drop_primary_key;
807                     $dropkey = true;
808                 }
809                 break;
810             case XMLDB_KEY_UNIQUE:
811                 if ($this->unique_keys) {
812                     $template = $this->drop_unique_key;
813                     $dropkey = true;
814                 }
815                 break;
816             case XMLDB_KEY_FOREIGN_UNIQUE:
817             case XMLDB_KEY_FOREIGN:
818                 if ($this->foreign_keys) {
819                     $template = $this->drop_foreign_key;
820                     $dropkey = true;
821                 }
822                 break;
823         }
824     /// If we have decided to drop the key, let's do it
825         if ($dropkey) {
826         /// Replace TABLENAME, CONSTRAINTTYPE and KEYNAME as needed
827             $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $template);
828             $dropsql = str_replace('KEYNAME', $dbkeyname, $dropsql);
830             $results[] = $dropsql;
831         }
833     /// If we aren't dropping the keys OR if the key is XMLDB_KEY_FOREIGN (not underlying index generated
834     /// automatically by the RDBMS) drop the underlying (created by us) index (if exists)
835         if (!$dropkey || $xmldb_key->getType() == XMLDB_KEY_FOREIGN) {
836         /// Only if they exist
837             $xmldb_index = new xmldb_index('anyname', XMLDB_INDEX_UNIQUE, $xmldb_key->getFields());
838             if ($this->mdb->get_manager()->index_exists($xmldb_table, $xmldb_index)) {
839                 $results = array_merge($results, $this->getDropIndexSQL($xmldb_table, $xmldb_index));
840             }
841         }
843     /// If the key is XMLDB_KEY_FOREIGN_UNIQUE, drop the UNIQUE too
844         if ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && $this->unique_keys) {
845         ///Duplicate the key
846             $xmldb_key->setType(XMLDB_KEY_UNIQUE);
847             $results = array_merge($results, $this->getDropKeySQL($xmldb_table, $xmldb_key));
848         }
850     /// Return results
851         return $results;
852     }
854     /**
855      * Given one xmldb_table and one xmldb_key, return the SQL statements needded to rename the key in the table
856      * Experimental! Shouldn't be used at all!
857      */
859     public function getRenameKeySQL($xmldb_table, $xmldb_key, $newname) {
861         $results = array();
863     /// Get the real key name
864         $dbkeyname = $this->mdb->get_manager()->find_key_name($xmldb_table, $xmldb_key);
866     /// Check we are really generating this type of keys
867         if (($xmldb_key->getType() == XMLDB_KEY_PRIMARY && !$this->primary_keys) ||
868             ($xmldb_key->getType() == XMLDB_KEY_UNIQUE && !$this->unique_keys) ||
869             ($xmldb_key->getType() == XMLDB_KEY_FOREIGN && !$this->foreign_keys) ||
870             ($xmldb_key->getType() == XMLDB_KEY_FOREIGN_UNIQUE && !$this->unique_keys && !$this->foreign_keys)) {
871         /// We aren't generating this type of keys, delegate to child indexes
872             $xmldb_index = new xmldb_index($xmldb_key->getName());
873             $xmldb_index->setFields($xmldb_key->getFields());
874             return $this->getRenameIndexSQL($xmldb_table, $xmldb_index, $newname);
875         }
877     /// Arrived here so we are working with keys, lets rename them
878     /// Replace TABLENAME and KEYNAME as needed
879         $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_key_sql);
880         $renamesql = str_replace('OLDKEYNAME', $dbkeyname, $renamesql);
881         $renamesql = str_replace('NEWKEYNAME', $newname, $renamesql);
883     /// Some DB doesn't support key renaming so this can be empty
884         if ($renamesql) {
885             $results[] = $renamesql;
886         }
888         return $results;
889     }
891     /**
892      * Given one xmldb_table and one xmldb_index, return the SQL statements needded to add the index to the table
893      */
894     public function getAddIndexSQL($xmldb_table, $xmldb_index) {
896     /// Just use the CreateIndexSQL function
897         return $this->getCreateIndexSQL($xmldb_table, $xmldb_index);
898     }
900     /**
901      * Given one xmldb_table and one xmldb_index, return the SQL statements needded to drop the index from the table
902      */
903     public function getDropIndexSQL($xmldb_table, $xmldb_index) {
905         $results = array();
907     /// Get the real index name
908         $dbindexname = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index);
910     /// Replace TABLENAME and INDEXNAME as needed
911         $dropsql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->drop_index_sql);
912         $dropsql = str_replace('INDEXNAME', $dbindexname, $dropsql);
914         $results[] = $dropsql;
916         return $results;
917     }
919     /**
920      * Given one xmldb_table and one xmldb_index, return the SQL statements needded to rename the index in the table
921      * Experimental! Shouldn't be used at all!
922      */
923     function getRenameIndexSQL($xmldb_table, $xmldb_index, $newname) {
924     /// Some DB doesn't support index renaming (MySQL) so this can be empty
925         if (empty($this->rename_index_sql)) {
926             return array();
927         }
929     /// Get the real index name
930         $dbindexname = $this->mdb->get_manager()->find_index_name($xmldb_table, $xmldb_index);
931     /// Replace TABLENAME and INDEXNAME as needed
932         $renamesql = str_replace('TABLENAME', $this->getTableName($xmldb_table), $this->rename_index_sql);
933         $renamesql = str_replace('OLDINDEXNAME', $dbindexname, $renamesql);
934         $renamesql = str_replace('NEWINDEXNAME', $newname, $renamesql);
936         return array($renamesql);
937     }
939     /**
940      * Given three strings (table name, list of fields (comma separated) and suffix),
941      * create the proper object name quoting it if necessary.
942      *
943      * IMPORTANT: This function must be used to CALCULATE NAMES of objects TO BE CREATED,
944      *            NEVER TO GUESS NAMES of EXISTING objects!!!
945      */
946     public function getNameForObject($tablename, $fields, $suffix='') {
948         $name = '';
950     /// Implement one basic cache to avoid object name duplication
951     /// along all the request life, but never to return cached results
952     /// We need this because sql statements are created before executing
953     /// them, hence names doesn't exist "physically" yet in DB, so we need
954     /// to known which ones have been used
955         if (!isset($used_names)) {
956             static $used_names = array();
957         }
959     /// Use standard naming. See http://docs.moodle.org/en/XMLDB_key_and_index_naming
960         $tablearr = explode ('_', $tablename);
961         foreach ($tablearr as $table) {
962             $name .= substr(trim($table),0,4);
963         }
964         $name .= '_';
965         $fieldsarr = explode (',', $fields);
966         foreach ($fieldsarr as $field) {
967             $name .= substr(trim($field),0,3);
968         }
969     /// Prepend the prefix
970         $name = $this->prefix . $name;
972         $name = substr(trim($name), 0, $this->names_max_length - 1 - strlen($suffix)); //Max names_max_length
974     /// Add the suffix
975         $namewithsuffix = $name;
976         if ($suffix) {
977             $namewithsuffix = $namewithsuffix . '_' . $suffix;
978         }
980     /// If the calculated name is in the cache, or if we detect it by introspecting the DB let's modify if
981         if (in_array($namewithsuffix, $used_names) || $this->isNameInUse($namewithsuffix, $suffix, $tablename)) {
982             $counter = 2;
983         /// If have free space, we add 2
984             if (strlen($namewithsuffix) < $this->names_max_length) {
985                 $newname = $name . $counter;
986         /// Else replace the last char by 2
987             } else {
988                 $newname = substr($name, 0, strlen($name)-1) . $counter;
989             }
990             $newnamewithsuffix = $newname;
991             if ($suffix) {
992                 $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
993             }
994         /// Now iterate until not used name is found, incrementing the counter
995             while (in_array($newnamewithsuffix, $used_names) || $this->isNameInUse($newnamewithsuffix, $suffix, $tablename)) {
996                 $counter++;
997                 $newname = substr($name, 0, strlen($newname)-1) . $counter;
998                 $newnamewithsuffix = $newname;
999                 if ($suffix) {
1000                     $newnamewithsuffix = $newnamewithsuffix . '_' . $suffix;
1001                 }
1002             }
1003             $namewithsuffix = $newnamewithsuffix;
1004         }
1006     /// Add the name to the cache
1007         $used_names[] = $namewithsuffix;
1009     /// Quote it if necessary (reserved words)
1010         $namewithsuffix = $this->getEncQuoted($namewithsuffix);
1012         return $namewithsuffix;
1013     }
1015     /**
1016      * Given any string (or one array), enclose it by the proper quotes
1017      * if it's a reserved word
1018      */
1019     public function getEncQuoted($input) {
1021         if (is_array($input)) {
1022             foreach ($input as $key=>$content) {
1023                 $input[$key] = $this->getEncQuoted($content);
1024             }
1025             return $input;
1026         } else {
1027         /// Always lowercase
1028             $input = strtolower($input);
1029         /// if reserved or quote_all, quote it
1030             if ($this->quote_all || in_array($input, $this->reserved_words)) {
1031                 $input = $this->quote_string . $input . $this->quote_string;
1032             }
1033             return $input;
1034         }
1035     }
1037     /**
1038      * Given one XMLDB Statement, build the needed SQL insert sentences to execute it
1039      */
1040     function getExecuteInsertSQL($statement) {
1042          $results = array();  //Array where all the sentences will be stored
1044          if ($sentences = $statement->getSentences()) {
1045              foreach ($sentences as $sentence) {
1046              /// Get the list of fields
1047                  $fields = $statement->getFieldsFromInsertSentence($sentence);
1048              /// Get the values of fields
1049                  $values = $statement->getValuesFromInsertSentence($sentence);
1050              /// Look if we have some CONCAT value and transform it dinamically
1051                  foreach($values as $key => $value) {
1052                  /// Trim single quotes
1053                      $value = trim($value,"'");
1054                      if (stristr($value, 'CONCAT') !== false){
1055                      /// Look for data between parentesis
1056                          preg_match("/CONCAT\s*\((.*)\)$/is", trim($value), $matches);
1057                          if (isset($matches[1])) {
1058                              $part = $matches[1];
1059                          /// Convert the comma separated string to an array
1060                              $arr = xmldb_object::comma2array($part);
1061                              if ($arr) {
1062                                  $value = $this->getConcatSQL($arr);
1063                              }
1064                          }
1065                      }
1066                  /// Values to be sent to DB must be properly escaped
1067                      $value = $this->addslashes($value);
1068                  /// Back trimmed quotes
1069                      $value = "'" . $value . "'";
1070                  /// Back to the array
1071                      $values[$key] = $value;
1072                  }
1074              /// Iterate over fields, escaping them if necessary
1075                  foreach($fields as $key => $field) {
1076                      $fields[$key] = $this->getEncQuoted($field);
1077                  }
1078              /// Build the final SQL sentence and add it to the array of results
1079              $sql = 'INSERT INTO ' . $this->getEncQuoted($this->prefix . $statement->getTable()) .
1080                          '(' . implode(', ', $fields) . ') ' .
1081                          'VALUES (' . implode(', ', $values) . ')';
1082                  $results[] = $sql;
1083              }
1085          }
1086          return $results;
1087     }
1089     /**
1090      * Given one array of elements, build de proper CONCAT expresion, based
1091      * in the $concat_character setting. If such setting is empty, then
1092      * MySQL's CONCAT function will be used instead
1093      */
1094     public function getConcatSQL($elements) {
1096     /// Replace double quoted elements by single quotes
1097         foreach($elements as $key => $element) {
1098             $element = trim($element);
1099             if (substr($element, 0, 1) == '"' &&
1100                 substr($element, -1, 1) == '"') {
1101                     $elements[$key] = "'" . trim($element, '"') . "'";
1102             }
1103         }
1105     /// Now call the standard $DB->sql_concat() DML function
1106         return call_user_func_array(array($this->mdb, 'sql_concat'), $elements);
1107     }
1109     /**
1110      * Returns the name (string) of the sequence used in the table for the autonumeric pk
1111      * Only some DB have this implemented
1112      */
1113     public function getSequenceFromDB($xmldb_table) {
1114         return false;
1115     }
1117     /**
1118      * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg)
1119      * return if such name is currently in use (true) or no (false)
1120      * (MySQL requires the whole xmldb_table object to be specified, so we add it always)
1121      * (invoked from getNameForObject()
1122      * Only some DB have this implemented
1123      */
1124     public function isNameInUse($object_name, $type, $table_name) {
1125         return false; //For generators not implementing introspecion,
1126                       //we always return with the name being free to be used
1127     }
1130 /// ALL THESE FUNCTION MUST BE CUSTOMISED BY ALL THE XMLDGenerator classes
1132     /**
1133      * Given one XMLDB Type, lenght and decimals, returns the DB proper SQL type
1134      */
1135     public abstract function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null);
1137     /**
1138      * Returns the code (array of statements) needed to execute extra statements on field rename
1139      */
1140     public function getRenameFieldExtraSQL($xmldb_table, $xmldb_field) {
1141         return array();
1142     }
1144     /**
1145      * Returns the code (array of statements) needed
1146      * to create one sequence for the xmldb_table and xmldb_field passes
1147      */
1148     public function getCreateSequenceSQL($xmldb_table, $xmldb_field) {
1149         return array();
1150     }
1152     /**
1153      * Returns the code (array of statements) needed to add one comment to the table
1154      */
1155     public abstract function getCommentSQL($xmldb_table);
1157     /**
1158      * Returns the code (array of statements) needed to execute extra statements on table rename
1159      */
1160     public function getRenameTableExtraSQL($xmldb_table) {
1161         return array();
1162     }
1164     /**
1165      * Returns the code (array of statements) needed to execute extra statements on table drop
1166      */
1167     public function getDropTableExtraSQL($xmldb_table) {
1168         return array();
1169     }
1171     /**
1172      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to drop its enum
1173      * (usually invoked from getModifyEnumSQL()
1174      *
1175      * TODO: Moodle 2.1 - Drop getDropEnumSQL()
1176      */
1177     public abstract function getDropEnumSQL($xmldb_table, $xmldb_field);
1179     /**
1180      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to drop its default
1181      * (usually invoked from getModifyDefaultSQL()
1182      *
1183      * TODO: Moodle 2.1 - Drop getDropDefaultSQL()
1184      */
1185     public abstract function getDropDefaultSQL($xmldb_table, $xmldb_field);
1187     /**
1188      * Given one xmldb_table and one optional xmldb_field, return one array with all the check
1189      * constrainst found for that table (or field). Must exist for each DB supported.
1190      * (usually invoked from find_check_constraint_name)
1191      *
1192      * TODO: Moodle 2.1 - Drop getCheckConstraintsFromDB
1193      */
1194     public abstract function getCheckConstraintsFromDB($xmldb_table, $xmldb_field=null);
1196     /**
1197      * Given one xmldb_table and one xmldb_field, return the SQL statements needded to add its default
1198      * (usually invoked from getModifyDefaultSQL()
1199      */
1200     public abstract function getCreateDefaultSQL($xmldb_table, $xmldb_field);
1202     /**
1203      * Returns an array of reserved words (lowercase) for this DB
1204      * You MUST provide the real list for each DB inside every XMLDB class
1205      */
1206     public static abstract function getReservedWords();
1208     /**
1209      * Returns all reserved works in supported databases.
1210      * Reserved words should be lowercase.
1211      * @return array ('word'=>array(databases))
1212      */
1213     public static function getAllReservedWords() {
1214         global $CFG;
1216         $generators = array('mysql', 'postgres', 'oracle', 'mssql', 'sqlite');
1217         $reserved_words = array();
1219         foreach($generators as $generator) {
1220             $class = $generator . '_sql_generator';
1221             require_once("$CFG->libdir/ddl/$class.php");
1222             foreach (call_user_func(array($class, 'getReservedWords')) as $word) {
1223                 $reserved_words[$word][] = $generator;
1224             }
1225         }
1226         ksort($reserved_words);
1227         return $reserved_words;
1228     }
1230     public function addslashes($s) {
1231         // do not use php addslashes() because it depends on PHP quote settings!
1232         $s = str_replace('\\','\\\\',$s);
1233         $s = str_replace("\0","\\\0", $s);
1234         $s = str_replace("'",  "\\'", $s);
1235         return $s;
1236     }