2067a44eb1c927441f3fe9add00ad3e139af4c44
[moodle.git] / lib / ddl / mssql_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  * MSSQL specific SQL code generator.
21  *
22  * @package    core
23  * @subpackage ddl
24  * @copyright  1999 onwards Martin Dougiamas     http://dougiamas.com
25  *             2001-3001 Eloy Lafuente (stronk7) http://contiento.com
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 require_once($CFG->libdir.'/ddl/sql_generator.php');
33 /// This class generate SQL code to be used against MSSQL
34 /// It extends XMLDBgenerator so everything can be
35 /// overridden as needed to generate correct SQL.
37 class mssql_sql_generator extends sql_generator {
39 /// Only set values that are different from the defaults present in XMLDBgenerator
41     public $statement_end = "\ngo"; // String to be automatically added at the end of each statement
43     public $number_type = 'DECIMAL';    // Proper type for NUMBER(x) in this DB
45     public $unsigned_allowed = false;    // To define in the generator must handle unsigned information
46     public $default_for_char = '';      // To define the default to set for NOT NULLs CHARs without default (null=do nothing)
48     public $specify_nulls = true;  //To force the generator if NULL clauses must be specified. It shouldn't be necessary
49                                      //but some mssql drivers require them or everything is created as NOT NULL :-(
51     public $sequence_extra_code = false; //Does the generator need to add extra code to generate the sequence fields
52     public $sequence_name = 'IDENTITY(1,1)'; //Particular name for inline sequences in this generator
53     public $sequence_only = false; //To avoid to output the rest of the field specs, leaving only the name and the sequence_name variable
55     public $enum_inline_code = false; //Does the generator need to add inline code in the column definition
57     public $add_table_comments  = false;  // Does the generator need to add code for table comments
59     public $concat_character = '+'; //Characters to be used as concatenation operator. If not defined
60                                   //MySQL CONCAT function will be use
62     public $rename_table_sql = "sp_rename 'OLDNAME', 'NEWNAME'"; //SQL sentence to rename one table, both
63                                   //OLDNAME and NEWNAME are dynamically replaced
65     public $rename_column_sql = "sp_rename 'TABLENAME.OLDFIELDNAME', 'NEWFIELDNAME', 'COLUMN'";
66                                       ///TABLENAME, OLDFIELDNAME and NEWFIELDNAME are dyanmically replaced
68     public $drop_index_sql = 'DROP INDEX TABLENAME.INDEXNAME'; //SQL sentence to drop one index
69                                                                //TABLENAME, INDEXNAME are dynamically replaced
71     public $rename_index_sql = "sp_rename 'TABLENAME.OLDINDEXNAME', 'NEWINDEXNAME', 'INDEX'"; //SQL sentence to rename one index
72                                       //TABLENAME, OLDINDEXNAME, NEWINDEXNAME are dynamically replaced
74     public $rename_key_sql = null; //SQL sentence to rename one key
75                                           //TABLENAME, OLDKEYNAME, NEWKEYNAME are dynamically replaced
77     /**
78      * Reset a sequence to the id field of a table.
79      * @param string $table name of table or xmldb_table object
80      * @return array sql commands to execute
81      */
82     public function getResetSequenceSQL($table) {
84         if (is_string($table)) {
85             $table = new xmldb_table($table);
86         }
88         // From http://msdn.microsoft.com/en-us/library/ms176057.aspx
89         $value = (int)$this->mdb->get_field_sql('SELECT MAX(id) FROM {'. $table->getName() . '}');
90         if ($value == 0) {
91             $value = 1;
92         }
93         return array("DBCC CHECKIDENT ('" . $this->getTableName($table) . "', RESEED, $value)");
94     }
96     /**
97      * Given one xmldb_table, returns it's correct name, depending of all the parametrization
98      * Overridden to allow change of names in temp tables
99      *
100      * @param xmldb_table table whose name we want
101      * @param boolean to specify if the name must be quoted (if reserved word, only!)
102      * @return string the correct name of the table
103      */
104     public function getTableName(xmldb_table $xmldb_table, $quoted=true) {
105     /// Get the name, supporting special mssql names for temp tables
106         if ($this->temptables->is_temptable($xmldb_table->getName())) {
107             $tablename = $this->temptables->get_correct_name($xmldb_table->getName());
108         } else {
109             $tablename = $this->prefix . $xmldb_table->getName();
110         }
112     /// Apply quotes optionally
113         if ($quoted) {
114             $tablename = $this->getEncQuoted($tablename);
115         }
117         return $tablename;
118     }
121     /**
122      * Given one correct xmldb_table, returns the SQL statements
123      * to create temporary table (inside one array)
124      */
125     public function getCreateTempTableSQL($xmldb_table) {
126         $this->temptables->add_temptable($xmldb_table->getName());
127         $sqlarr = $this->getCreateTableSQL($xmldb_table);
128         return $sqlarr;
129     }
131     /**
132      * Given one correct xmldb_table and the new name, returns the SQL statements
133      * to drop it (inside one array)
134      */
135     public function getDropTempTableSQL($xmldb_table) {
136         $sqlarr = $this->getDropTableSQL($xmldb_table);
137         $this->temptables->delete_temptable($xmldb_table->getName());
138         return $sqlarr;
139     }
141     /**
142      * Given one XMLDB Type, lenght and decimals, returns the DB proper SQL type
143      */
144     public function getTypeSQL($xmldb_type, $xmldb_length=null, $xmldb_decimals=null) {
146         switch ($xmldb_type) {
147             case XMLDB_TYPE_INTEGER:    // From http://msdn.microsoft.com/library/en-us/tsqlref/ts_da-db_7msw.asp?frame=true
148                 if (empty($xmldb_length)) {
149                     $xmldb_length = 10;
150                 }
151                 if ($xmldb_length > 9) {
152                     $dbtype = 'BIGINT';
153                 } else if ($xmldb_length > 4) {
154                     $dbtype = 'INTEGER';
155                 } else {
156                     $dbtype = 'SMALLINT';
157                 }
158                 break;
159             case XMLDB_TYPE_NUMBER:
160                 $dbtype = $this->number_type;
161                 if (!empty($xmldb_length)) {
162                 /// 38 is the max allowed
163                     if ($xmldb_length > 38) {
164                         $xmldb_length = 38;
165                     }
166                     $dbtype .= '(' . $xmldb_length;
167                     if (!empty($xmldb_decimals)) {
168                         $dbtype .= ',' . $xmldb_decimals;
169                     }
170                     $dbtype .= ')';
171                 }
172                 break;
173             case XMLDB_TYPE_FLOAT:
174                 $dbtype = 'FLOAT';
175                 if (!empty($xmldb_decimals)) {
176                     if ($xmldb_decimals < 6) {
177                         $dbtype = 'REAL';
178                     }
179                 }
180                 break;
181             case XMLDB_TYPE_CHAR:
182                 $dbtype = 'NVARCHAR';
183                 if (empty($xmldb_length)) {
184                     $xmldb_length='255';
185                 }
186                 $dbtype .= '(' . $xmldb_length . ')';
187                 break;
188             case XMLDB_TYPE_TEXT:
189                 $dbtype = 'NTEXT';
190                 break;
191             case XMLDB_TYPE_BINARY:
192                 $dbtype = 'IMAGE';
193                 break;
194             case XMLDB_TYPE_DATETIME:
195                 $dbtype = 'DATETIME';
196                 break;
197         }
198         return $dbtype;
199     }
201     /**
202      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop the field from the table
203      * MSSQL overwrites the standard sentence because it needs to do some extra work dropping the default and
204      * check constraints
205      */
206     public function getDropFieldSQL($xmldb_table, $xmldb_field) {
207         $results = array();
209     /// Get the quoted name of the table and field
210         $tablename = $this->getTableName($xmldb_table);
211         $fieldname = $this->getEncQuoted($xmldb_field->getName());
213     /// Look for any default constraint in this field and drop it
214         if ($defaultname = $this->getDefaultConstraintName($xmldb_table, $xmldb_field)) {
215             $results[] = 'ALTER TABLE ' . $tablename . ' DROP CONSTRAINT ' . $defaultname;
216         }
218     /// Look for any check constraint in this field and drop it
219         if ($drop_check = $this->getDropEnumSQL($xmldb_table, $xmldb_field)) {
220             $results = array_merge($results, $drop_check);
221         }
223     /// Build the standard alter table drop column
224         $results[] = 'ALTER TABLE ' . $tablename . ' DROP COLUMN ' . $fieldname;
226         return $results;
227     }
229     /**
230      * Given one correct xmldb_field and the new name, returns the SQL statements
231      * to rename it (inside one array)
232      * MSSQL is special, so we overload the function here. It needs to
233      * drop the constraints BEFORE renaming the field
234      */
235     public function getRenameFieldSQL($xmldb_table, $xmldb_field, $newname) {
237         $results = array();  //Array where all the sentences will be stored
239     /// Although this is checked in database_manager::rename_field() - double check
240     /// that we aren't trying to rename one "id" field. Although it could be
241     /// implemented (if adding the necessary code to rename sequences, defaults,
242     /// triggers... and so on under each getRenameFieldExtraSQL() function, it's
243     /// better to forbid it, mainly because this field is the default PK and
244     /// in the future, a lot of FKs can be pointing here. So, this field, more
245     /// or less, must be considered immutable!
246         if ($xmldb_field->getName() == 'id') {
247             return array();
248         }
250     /// Call to standard (parent) getRenameFieldSQL() function
251         $results = array_merge($results, parent::getRenameFieldSQL($xmldb_table, $xmldb_field, $newname));
253         return $results;
254     }
256     /**
257      * Returns the code (array of statements) needed to execute extra statements on table rename
258      */
259     public function getRenameTableExtraSQL($xmldb_table, $newname) {
261         $results = array();
263         $newt = new xmldb_table($newname); //Temporal table for name calculations
265         $oldtablename = $this->getTableName($xmldb_table);
266         $newtablename = $this->getTableName($newt);
268     /// Rename all the check constraints in the table
269         $oldconstraintprefix = $this->getNameForObject($xmldb_table->getName(), '');
270         $newconstraintprefix = $this->getNameForObject($newt->getName(), '', '');
272         if ($constraints = $this->getCheckConstraintsFromDB($xmldb_table)) {
273             foreach ($constraints as $constraint) {
274             /// Drop the old constraint
275                 $results[] = 'ALTER TABLE ' . $newtablename . ' DROP CONSTRAINT ' . $constraint->name;
276             }
277         }
279         return $results;
280     }
282     /**
283      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to alter the field in the table
284      */
285     public function getAlterFieldSQL($xmldb_table, $xmldb_field, $skip_type_clause = NULL, $skip_default_clause = NULL, $skip_notnull_clause = NULL) {
287         $results = array(); /// To store all the needed SQL commands
289     /// Get the quoted name of the table and field
290         $tablename = $xmldb_table->getName();
291         $fieldname = $xmldb_field->getName();
293     /// Take a look to field metadata
294         $meta = $this->mdb->get_columns($tablename);
295         $metac = $meta[$fieldname];
296         $oldmetatype = $metac->meta_type;
298         $oldlength = $metac->max_length;
299         $olddecimals = empty($metac->scale) ? null : $metac->scale;
300         $oldnotnull = empty($metac->not_null) ? false : $metac->not_null;
301         $olddefault = empty($metac->has_default) ? null : strtok($metac->default_value, ':');
303         $typechanged = true;  //By default, assume that the column type has changed
304         $lengthchanged = true;  //By default, assume that the column length has changed
306     /// Detect if we are changing the type of the column
307         if (($xmldb_field->getType() == XMLDB_TYPE_INTEGER && $oldmetatype == 'I') ||
308             ($xmldb_field->getType() == XMLDB_TYPE_NUMBER  && $oldmetatype == 'N') ||
309             ($xmldb_field->getType() == XMLDB_TYPE_FLOAT   && $oldmetatype == 'F') ||
310             ($xmldb_field->getType() == XMLDB_TYPE_CHAR    && $oldmetatype == 'C') ||
311             ($xmldb_field->getType() == XMLDB_TYPE_TEXT    && $oldmetatype == 'X') ||
312             ($xmldb_field->getType() == XMLDB_TYPE_BINARY  && $oldmetatype == 'B')) {
313             $typechanged = false;
314         }
316     /// If the new field (and old) specs are for integer, let's be a bit more specific differentiating
317     /// types of integers. Else, some combinations can cause things like MDL-21868
318         if ($xmldb_field->getType() == XMLDB_TYPE_INTEGER && $oldmetatype == 'I') {
319             if ($xmldb_field->getLength() > 9) { // Convert our new lenghts to detailed meta types
320                 $newmssqlinttype = 'I8';
321             } else if ($xmldb_field->getLength() > 4) {
322                 $newmssqlinttype = 'I';
323             } else {
324                 $newmssqlinttype = 'I2';
325             }
326             if ($metac->type == 'bigint') { // Convert current DB type to detailed meta type (our metatype is not enough!)
327                 $oldmssqlinttype = 'I8';
328             } else if ($metac->type == 'smallint') {
329                 $oldmssqlinttype = 'I2';
330             } else {
331                 $oldmssqlinttype = 'I';
332             }
333             if ($newmssqlinttype != $oldmssqlinttype) { // Compare new and old meta types
334                 $typechanged = true; // Change in meta type means change in type at all effects
335             }
336         }
338     /// Detect if we are changing the length of the column, not always necessary to drop defaults
339     /// if only the length changes, but it's safe to do it always
340         if ($xmldb_field->getLength() == $oldlength) {
341             $lengthchanged = false;
342         }
344     /// If type or length have changed drop the default if exists
345         if ($typechanged || $lengthchanged) {
346             $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field);
347         }
349     /// Some changes of type require multiple alter statements, because mssql lacks direct implicit cast between such types
350     /// Here it is the matrix: http://msdn.microsoft.com/en-us/library/ms187928(SQL.90).aspx
351     /// Going to store such intermediate alters in array of objects, storing all the info needed
352         $multiple_alter_stmt = array();
353         $targettype = $xmldb_field->getType();
355         if ($targettype == XMLDB_TYPE_TEXT && $oldmetatype == 'I') { // integer to text
356             $multiple_alter_stmt[0] = new stdClass;                  // needs conversion to varchar
357             $multiple_alter_stmt[0]->type = XMLDB_TYPE_CHAR;
358             $multiple_alter_stmt[0]->length = 255;
360         } else if ($targettype == XMLDB_TYPE_TEXT && $oldmetatype == 'N') { // decimal to text
361             $multiple_alter_stmt[0] = new stdClass;                         // needs conversion to varchar
362             $multiple_alter_stmt[0]->type = XMLDB_TYPE_CHAR;
363             $multiple_alter_stmt[0]->length = 255;
365         } else if ($targettype == XMLDB_TYPE_TEXT && $oldmetatype == 'F') { // float to text
366             $multiple_alter_stmt[0] = new stdClass;                         // needs conversion to varchar
367             $multiple_alter_stmt[0]->type = XMLDB_TYPE_CHAR;
368             $multiple_alter_stmt[0]->length = 255;
370         } else if ($targettype == XMLDB_TYPE_INTEGER && $oldmetatype == 'X') { // text to integer
371             $multiple_alter_stmt[0] = new stdClass;                            // needs conversion to varchar
372             $multiple_alter_stmt[0]->type = XMLDB_TYPE_CHAR;
373             $multiple_alter_stmt[0]->length = 255;
374             $multiple_alter_stmt[1] = new stdClass;                            // and also needs conversion to decimal
375             $multiple_alter_stmt[1]->type = XMLDB_TYPE_NUMBER;                 // without decimal positions
376             $multiple_alter_stmt[1]->length = 10;
378         } else if ($targettype == XMLDB_TYPE_NUMBER && $oldmetatype == 'X') { // text to decimal
379             $multiple_alter_stmt[0] = new stdClass;                           // needs conversion to varchar
380             $multiple_alter_stmt[0]->type = XMLDB_TYPE_CHAR;
381             $multiple_alter_stmt[0]->length = 255;
383         } else if ($targettype ==  XMLDB_TYPE_FLOAT && $oldmetatype == 'X') { // text to float
384             $multiple_alter_stmt[0] = new stdClass;                           // needs conversion to varchar
385             $multiple_alter_stmt[0]->type = XMLDB_TYPE_CHAR;
386             $multiple_alter_stmt[0]->length = 255;
387         }
389     /// Just prevent default clauses in this type of sentences for mssql and launch the parent one
390         if (empty($multiple_alter_stmt)) { // Direct implicit conversion allowed, launch it
391             $results = array_merge($results, parent::getAlterFieldSQL($xmldb_table, $xmldb_field, NULL, true, NULL));
393         } else { // Direct implicit conversion forbidden, use the intermediate ones
394             $final_type = $xmldb_field->getType(); // Save final type and length
395             $final_length = $xmldb_field->getLength();
396             foreach ($multiple_alter_stmt as $alter) {
397                 $xmldb_field->setType($alter->type);  // Put our intermediate type and length and alter to it
398                 $xmldb_field->setLength($alter->length);
399                 $results = array_merge($results, parent::getAlterFieldSQL($xmldb_table, $xmldb_field, NULL, true, NULL));
400             }
401             $xmldb_field->setType($final_type); // Set the final type and length and alter to it
402             $xmldb_field->setLength($final_length);
403             $results = array_merge($results, parent::getAlterFieldSQL($xmldb_table, $xmldb_field, NULL, true, NULL));
404         }
406     /// Finally, process the default clause to add it back if necessary
407         if ($typechanged || $lengthchanged) {
408             $results = array_merge($results, $this->getCreateDefaultSQL($xmldb_table, $xmldb_field));
409         }
411     /// Return results
412         return $results;
413     }
415     /**
416      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to modify the default of the field in the table
417      */
418     public function getModifyDefaultSQL($xmldb_table, $xmldb_field) {
419     /// MSSQL is a bit special with default constraints because it implements them as external constraints so
420     /// normal ALTER TABLE ALTER COLUMN don't work to change defaults. Because this, we have this method overloaded here
422         $results = array();
424     /// Decide if we are going to create/modify or to drop the default
425         if ($xmldb_field->getDefault() === null) {
426             $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop but, under some circumstances, re-enable
427             $default_clause = $this->getDefaultClause($xmldb_field);
428             if ($default_clause) { //If getDefaultClause() it must have one default, create it
429                 $results = array_merge($results, $this->getCreateDefaultSQL($xmldb_table, $xmldb_field)); //Create/modify
430             }
431         } else {
432             $results = $this->getDropDefaultSQL($xmldb_table, $xmldb_field); //Drop (only if exists)
433             $results = array_merge($results, $this->getCreateDefaultSQL($xmldb_table, $xmldb_field)); //Create/modify
434         }
436         return $results;
437     }
439     /**
440      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its enum
441      * (usually invoked from getModifyEnumSQL()
442      *
443      * TODO: Moodle 2.1 - drop in Moodle 2.1
444      */
445     public function getDropEnumSQL($xmldb_table, $xmldb_field) {
446     /// Let's introspect to know the real name of the check constraint
447         if ($check_constraints = $this->getCheckConstraintsFromDB($xmldb_table, $xmldb_field)) {
448             $check_constraint = array_shift($check_constraints); /// Get the 1st (should be only one)
449             $constraint_name = strtolower($check_constraint->name); /// Extract the REAL name
450         /// All we have to do is to drop the check constraint
451             return array('ALTER TABLE ' . $this->getTableName($xmldb_table) .
452                      ' DROP CONSTRAINT ' . $constraint_name);
453         } else { /// Constraint not found. Nothing to do
454             return array();
455         }
456     }
458     /**
459      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to create its default
460      * (usually invoked from getModifyDefaultSQL()
461      */
462     public function getCreateDefaultSQL($xmldb_table, $xmldb_field) {
463     /// MSSQL is a bit special and it requires the corresponding DEFAULT CONSTRAINT to be dropped
465         $results = array();
467     /// Get the quoted name of the table and field
468         $tablename = $this->getTableName($xmldb_table);
469         $fieldname = $this->getEncQuoted($xmldb_field->getName());
471     /// Now, check if, with the current field attributes, we have to build one default
472         $default_clause = $this->getDefaultClause($xmldb_field);
473         if ($default_clause) {
474         /// We need to build the default (Moodle) default, so do it
475             $sql = 'ALTER TABLE ' . $tablename . ' ADD' . $default_clause . ' FOR ' . $fieldname;
476             $results[] = $sql;
477         }
479         return $results;
480     }
482     /**
483      * Given one xmldb_table and one xmldb_field, return the SQL statements needed to drop its default
484      * (usually invoked from getModifyDefaultSQL()
485      */
486     public function getDropDefaultSQL($xmldb_table, $xmldb_field) {
487     /// MSSQL is a bit special and it requires the corresponding DEFAULT CONSTRAINT to be dropped
489         $results = array();
491     /// Get the quoted name of the table and field
492         $tablename = $this->getTableName($xmldb_table);
493         $fieldname = $this->getEncQuoted($xmldb_field->getName());
495     /// Look for the default contraint and, if found, drop it
496         if ($defaultname = $this->getDefaultConstraintName($xmldb_table, $xmldb_field)) {
497             $results[] = 'ALTER TABLE ' . $tablename . ' DROP CONSTRAINT ' . $defaultname;
498         }
500         return $results;
501     }
503     /**
504      * Given one xmldb_table and one xmldb_field, returns the name of its default constraint in DB
505      * or false if not found
506      * This function should be considered internal and never used outside from generator
507      */
508     public function getDefaultConstraintName($xmldb_table, $xmldb_field) {
510     /// Get the quoted name of the table and field
511         $tablename = $this->getTableName($xmldb_table);
512         $fieldname = $xmldb_field->getName();
514     /// Look for any default constraint in this field and drop it
515         if ($default = $this->mdb->get_record_sql("SELECT id, object_name(cdefault) AS defaultconstraint
516                                                      FROM syscolumns
517                                                     WHERE id = object_id(?)
518                                                           AND name = ?", array($tablename, $fieldname))) {
519             return $default->defaultconstraint;
520         } else {
521             return false;
522         }
523     }
525     /**
526      * Given one xmldb_table returns one array with all the check constraints
527      * in the table (fetched from DB)
528      * Optionally the function allows one xmldb_field to be specified in
529      * order to return only the check constraints belonging to one field.
530      * Each element contains the name of the constraint and its description
531      * If no check constraints are found, returns an empty array
532      *
533      * TODO: Moodle 2.1 - drop in Moodle 2.1
534      */
535     public function getCheckConstraintsFromDB($xmldb_table, $xmldb_field = null) {
538         $results = array();
540         $tablename = $this->getTableName($xmldb_table);
542         if ($constraints = $this->mdb->get_records_sql("SELECT o.name, c.text AS description
543                                                           FROM sysobjects o,
544                                                                sysobjects p,
545                                                                syscomments c
546                                                          WHERE p.id = o.parent_obj
547                                                                AND o.id = c.id
548                                                                AND o.xtype = 'C'
549                                                                AND p.name = ?", array($tablename))) {
550             foreach ($constraints as $constraint) {
551                 $results[$constraint->name] = $constraint;
552             }
553         }
555     /// Filter by the required field if specified
556         if ($xmldb_field) {
557             $filtered_results = array();
558             $filter = $xmldb_field->getName();
559         /// Lets clean a bit each constraint description, looking for the filtered field
560             foreach ($results as $key => $result) {
561                 $description = trim(preg_replace('/[\(\)]/', '',  $result->description));   // Parenthesis out & trim
562                 /// description starts by [$filter] assume it's a constraint belonging to the field
563                 if (preg_match("/^\[{$filter}\]/i", $description)) {
564                     $filtered_results[$key] = $result;
565                 }
566             }
567         /// Assign filtered results to the final results array
568             $results =  $filtered_results;
569         }
571         return $results;
572     }
574     /**
575      * Given three strings (table name, list of fields (comma separated) and suffix),
576      * create the proper object name quoting it if necessary.
577      *
578      * IMPORTANT: This function must be used to CALCULATE NAMES of objects TO BE CREATED,
579      *            NEVER TO GUESS NAMES of EXISTING objects!!!
580      *
581      * IMPORTANT: We are overriding this function for the MSSQL generator because objects
582      * belonging to temporary tables aren't searchable in the catalog neither in information
583      * schema tables. So, for temporary tables, we are going to add 4 randomly named "virtual"
584      * fields, so the generated names won't cause concurrency problems. Really nasty hack,
585      * but the alternative involves modifying all the creation table code to avoid naming
586      * constraints for temp objects and that will dupe a lot of code.
587      *
588      */
589     public function getNameForObject($tablename, $fields, $suffix='') {
590         if ($this->temptables->is_temptable($tablename)) { // Is temp table, inject random field names
591             $random = strtolower(random_string(12)); // 12cc to be split in 4 parts
592             $fields = $fields . ', ' . implode(', ', str_split($random, 3));
593         }
594         return parent::getNameForObject($tablename, $fields, $suffix); // Delegate to parent (common) algorithm
595     }
597     /**
598      * Given one object name and it's type (pk, uk, fk, ck, ix, uix, seq, trg)
599      * return if such name is currently in use (true) or no (false)
600      * (invoked from getNameForObject()
601      */
602     public function isNameInUse($object_name, $type, $table_name) {
603         switch($type) {
604             case 'seq':
605             case 'trg':
606             case 'pk':
607             case 'uk':
608             case 'fk':
609             case 'ck':
610                 if ($check = $this->mdb->get_records_sql("SELECT name
611                                                             FROM sysobjects
612                                                            WHERE lower(name) = ?", array(strtolower($object_name)))) {
613                     return true;
614                 }
615                 break;
616             case 'ix':
617             case 'uix':
618                 if ($check = $this->mdb->get_records_sql("SELECT name
619                                                             FROM sysindexes
620                                                            WHERE lower(name) = ?", array(strtolower($object_name)))) {
621                     return true;
622                 }
623                 break;
624         }
625         return false; //No name in use found
626     }
628     /**
629      * Returns the code (in array) needed to add one comment to the table
630      */
631     public function getCommentSQL($xmldb_table) {
632         return array();
633     }
635     public function addslashes($s) {
636         // do not use php addslashes() because it depends on PHP quote settings!
637         $s = str_replace("'",  "''", $s);
638         return $s;
639     }
641     /**
642      * Returns an array of reserved words (lowercase) for this DB
643      */
644     public static function getReservedWords() {
645     /// This file contains the reserved words for MSSQL databases
646     /// from http://msdn2.microsoft.com/en-us/library/ms189822.aspx
647         $reserved_words = array (
648             'add', 'all', 'alter', 'and', 'any', 'as', 'asc', 'authorization',
649             'avg', 'backup', 'begin', 'between', 'break', 'browse', 'bulk',
650             'by', 'cascade', 'case', 'check', 'checkpoint', 'close', 'clustered',
651             'coalesce', 'collate', 'column', 'commit', 'committed', 'compute',
652             'confirm', 'constraint', 'contains', 'containstable', 'continue',
653             'controlrow', 'convert', 'count', 'create', 'cross', 'current',
654             'current_date', 'current_time', 'current_timestamp', 'current_user',
655             'cursor', 'database', 'dbcc', 'deallocate', 'declare', 'default', 'delete',
656             'deny', 'desc', 'disk', 'distinct', 'distributed', 'double', 'drop', 'dummy',
657             'dump', 'else', 'end', 'errlvl', 'errorexit', 'escape', 'except', 'exec',
658             'execute', 'exists', 'exit', 'external', 'fetch', 'file', 'fillfactor', 'floppy',
659             'for', 'foreign', 'freetext', 'freetexttable', 'from', 'full', 'function',
660             'goto', 'grant', 'group', 'having', 'holdlock', 'identity', 'identitycol',
661             'identity_insert', 'if', 'in', 'index', 'inner', 'insert', 'intersect', 'into',
662             'is', 'isolation', 'join', 'key', 'kill', 'left', 'level', 'like', 'lineno',
663             'load', 'max', 'min', 'mirrorexit', 'national', 'nocheck', 'nonclustered',
664             'not', 'null', 'nullif', 'of', 'off', 'offsets', 'on', 'once', 'only', 'open',
665             'opendatasource', 'openquery', 'openrowset', 'openxml', 'option', 'or', 'order',
666             'outer', 'over', 'percent', 'perm', 'permanent', 'pipe', 'pivot', 'plan', 'precision',
667             'prepare', 'primary', 'print', 'privileges', 'proc', 'procedure', 'processexit',
668             'public', 'raiserror', 'read', 'readtext', 'reconfigure', 'references',
669             'repeatable', 'replication', 'restore', 'restrict', 'return', 'revoke',
670             'right', 'rollback', 'rowcount', 'rowguidcol', 'rule', 'save', 'schema',
671             'select', 'serializable', 'session_user', 'set', 'setuser', 'shutdown', 'some',
672             'statistics', 'sum', 'system_user', 'table', 'tape', 'temp', 'temporary',
673             'textsize', 'then', 'to', 'top', 'tran', 'transaction', 'trigger', 'truncate',
674             'tsequal', 'uncommitted', 'union', 'unique', 'update', 'updatetext', 'use',
675             'user', 'values', 'varying', 'view', 'waitfor', 'when', 'where', 'while',
676             'with', 'work', 'writetext'
677         );
678         return $reserved_words;
679     }