]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/database/MysqlManager.php
Release 6.5.15
[Github/sugarcrm.git] / include / database / MysqlManager.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40 * Description: This file handles the Data base functionality for the application.
41 * It acts as the DB abstraction layer for the application. It depends on helper classes
42 * which generate the necessary SQL. This sql is then passed to PEAR DB classes.
43 * The helper class is chosen in DBManagerFactory, which is driven by 'db_type' in 'dbconfig' under config.php.
44 *
45 * All the functions in this class will work with any bean which implements the meta interface.
46 * The passed bean is passed to helper class which uses these functions to generate correct sql.
47 *
48 * The meta interface has the following functions:
49 * getTableName()                        Returns table name of the object.
50 * getFieldDefinitions()         Returns a collection of field definitions in order.
51 * getFieldDefintion(name)               Return field definition for the field.
52 * getFieldValue(name)           Returns the value of the field identified by name.
53 *                               If the field is not set, the function will return boolean FALSE.
54 * getPrimaryFieldDefinition()   Returns the field definition for primary key
55 *
56 * The field definition is an array with the following keys:
57 *
58 * name          This represents name of the field. This is a required field.
59 * type          This represents type of the field. This is a required field and valid values are:
60 *           �   int
61 *           �   long
62 *           �   varchar
63 *           �   text
64 *           �   date
65 *           �   datetime
66 *           �   double
67 *           �   float
68 *           �   uint
69 *           �   ulong
70 *           �   time
71 *           �   short
72 *           �   enum
73 * length    This is used only when the type is varchar and denotes the length of the string.
74 *           The max value is 255.
75 * enumvals  This is a list of valid values for an enum separated by "|".
76 *           It is used only if the type is �enum�;
77 * required  This field dictates whether it is a required value.
78 *           The default value is �FALSE�.
79 * isPrimary This field identifies the primary key of the table.
80 *           If none of the fields have this flag set to �TRUE�,
81 *           the first field definition is assume to be the primary key.
82 *           Default value for this field is �FALSE�.
83 * default   This field sets the default value for the field definition.
84 *
85 *
86 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
87 * All Rights Reserved.
88 * Contributor(s): ______________________________________..
89 ********************************************************************************/
90
91 /**
92  * MySQL manager implementation for mysql extension
93  */
94 class MysqlManager extends DBManager
95 {
96         /**
97          * @see DBManager::$dbType
98          */
99         public $dbType = 'mysql';
100         public $variant = 'mysql';
101         public $dbName = 'MySQL';
102         public $label = 'LBL_MYSQL';
103
104         protected $maxNameLengths = array(
105                 'table' => 64,
106                 'column' => 64,
107                 'index' => 64,
108                 'alias' => 256
109         );
110
111         protected $type_map = array(
112                         'int'      => 'int',
113                         'double'   => 'double',
114                         'float'    => 'float',
115                         'uint'     => 'int unsigned',
116                         'ulong'    => 'bigint unsigned',
117                         'long'     => 'bigint',
118                         'short'    => 'smallint',
119                         'varchar'  => 'varchar',
120                         'text'     => 'text',
121                         'longtext' => 'longtext',
122                         'date'     => 'date',
123                         'enum'     => 'varchar',
124                         'relate'   => 'varchar',
125                         'multienum'=> 'text',
126                         'html'     => 'text',
127                         'longhtml' => 'longtext',
128                         'datetime' => 'datetime',
129                         'datetimecombo' => 'datetime',
130                         'time'     => 'time',
131                         'bool'     => 'bool',
132                         'tinyint'  => 'tinyint',
133                         'char'     => 'char',
134                         'blob'     => 'blob',
135                         'longblob' => 'longblob',
136                         'currency' => 'decimal(26,6)',
137                         'decimal'  => 'decimal',
138                         'decimal2' => 'decimal',
139                         'id'       => 'char(36)',
140                         'url'      => 'varchar',
141                         'encrypt'  => 'varchar',
142                         'file'     => 'varchar',
143                         'decimal_tpl' => 'decimal(%d, %d)',
144
145         );
146
147         protected $capabilities = array(
148                 "affected_rows" => true,
149                 "select_rows" => true,
150                 "inline_keys" => true,
151                 "create_user" => true,
152                 "fulltext" => true,
153             "collation" => true,
154             "create_db" => true,
155             "disable_keys" => true,
156         );
157
158         /**
159          * Parses and runs queries
160          *
161          * @param  string   $sql        SQL Statement to execute
162          * @param  bool     $dieOnError True if we want to call die if the query returns errors
163          * @param  string   $msg        Message to log if error occurs
164          * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
165          * @param  bool     $keepResult True if we want to push this result into the $lastResult array.
166          * @return resource result set
167          */
168         public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false)
169         {
170                 if(is_array($sql)) {
171                         return $this->queryArray($sql, $dieOnError, $msg, $suppress);
172                 }
173
174                 parent::countQuery($sql);
175                 $GLOBALS['log']->info('Query:' . $sql);
176                 $this->checkConnection();
177                 $this->query_time = microtime(true);
178                 $this->lastsql = $sql;
179                 $result = $suppress?@mysql_query($sql, $this->database):mysql_query($sql, $this->database);
180
181                 $this->query_time = microtime(true) - $this->query_time;
182                 $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
183
184
185                 if($keepResult)
186                         $this->lastResult = $result;
187
188                 $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
189                 return $result;
190         }
191
192     /**
193      * Returns the number of rows affected by the last query
194      * @param $result
195      * @return int
196      */
197         public function getAffectedRowCount($result)
198         {
199                 return mysql_affected_rows($this->getDatabase());
200         }
201
202         /**
203          * Returns the number of rows returned by the result
204          *
205          * This function can't be reliably implemented on most DB, do not use it.
206          * @abstract
207          * @deprecated
208          * @param  resource $result
209          * @return int
210          */
211         public function getRowCount($result)
212         {
213             return mysql_num_rows($result);
214         }
215
216         /**
217          * Disconnects from the database
218          *
219          * Also handles any cleanup needed
220          */
221         public function disconnect()
222         {
223                 $GLOBALS['log']->debug('Calling MySQL::disconnect()');
224                 if(!empty($this->database)){
225                         $this->freeResult();
226                         mysql_close($this->database);
227                         $this->database = null;
228                 }
229         }
230
231         /**
232          * @see DBManager::freeDbResult()
233          */
234         protected function freeDbResult($dbResult)
235         {
236                 if(!empty($dbResult))
237                         mysql_free_result($dbResult);
238         }
239
240
241         /**
242          * @abstract
243          * Check if query has LIMIT clause
244          * Relevant for now only for Mysql
245          * @param string $sql
246          * @return bool
247          */
248         protected function hasLimit($sql)
249         {
250             return stripos($sql, " limit ") !== false;
251         }
252
253         /**
254          * @see DBManager::limitQuery()
255          */
256         public function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true)
257         {
258         $start = (int)$start;
259         $count = (int)$count;
260             if ($start < 0)
261                         $start = 0;
262                 $GLOBALS['log']->debug('Limit Query:' . $sql. ' Start: ' .$start . ' count: ' . $count);
263
264             $sql = "$sql LIMIT $start,$count";
265                 $this->lastsql = $sql;
266
267                 if(!empty($GLOBALS['sugar_config']['check_query'])){
268                         $this->checkQuery($sql);
269                 }
270                 if(!$execute) {
271                         return $sql;
272                 }
273
274                 return $this->query($sql, $dieOnError, $msg);
275         }
276
277
278         /**
279          * @see DBManager::checkQuery()
280          */
281         protected function checkQuery($sql)
282         {
283                 $result   = $this->query('EXPLAIN ' . $sql);
284                 $badQuery = array();
285                 while ($row = $this->fetchByAssoc($result)) {
286                         if (empty($row['table']))
287                                 continue;
288                         $badQuery[$row['table']] = '';
289                         if (strtoupper($row['type']) == 'ALL')
290                                 $badQuery[$row['table']]  .=  ' Full Table Scan;';
291                         if (empty($row['key']))
292                                 $badQuery[$row['table']] .= ' No Index Key Used;';
293                         if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using filesort') > 0)
294                                 $badQuery[$row['table']] .= ' Using FileSort;';
295                         if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using temporary') > 0)
296                                 $badQuery[$row['table']] .= ' Using Temporary Table;';
297                 }
298
299                 if ( empty($badQuery) )
300                         return true;
301
302                 foreach($badQuery as $table=>$data ){
303                         if(!empty($data)){
304                                 $warning = ' Table:' . $table . ' Data:' . $data;
305                                 if(!empty($GLOBALS['sugar_config']['check_query_log'])){
306                                         $GLOBALS['log']->fatal($sql);
307                                         $GLOBALS['log']->fatal('CHECK QUERY:' .$warning);
308                                 }
309                                 else{
310                                         $GLOBALS['log']->warn('CHECK QUERY:' .$warning);
311                                 }
312                         }
313                 }
314
315                 return false;
316         }
317
318         /**
319          * @see DBManager::get_columns()
320          */
321         public function get_columns($tablename)
322         {
323                 //find all unique indexes and primary keys.
324                 $result = $this->query("DESCRIBE $tablename");
325
326                 $columns = array();
327                 while (($row=$this->fetchByAssoc($result)) !=null) {
328                         $name = strtolower($row['Field']);
329                         $columns[$name]['name']=$name;
330                         $matches = array();
331                         preg_match_all('/(\w+)(?:\(([0-9]+,?[0-9]*)\)|)( unsigned)?/i', $row['Type'], $matches);
332                         $columns[$name]['type']=strtolower($matches[1][0]);
333                         if ( isset($matches[2][0]) && in_array(strtolower($matches[1][0]),array('varchar','char','varchar2','int','decimal','float')) )
334                                 $columns[$name]['len']=strtolower($matches[2][0]);
335                         if ( stristr($row['Extra'],'auto_increment') )
336                                 $columns[$name]['auto_increment'] = '1';
337                         if ($row['Null'] == 'NO' && !stristr($row['Key'],'PRI'))
338                                 $columns[$name]['required'] = 'true';
339                         if (!empty($row['Default']) )
340                                 $columns[$name]['default'] = $row['Default'];
341                 }
342                 return $columns;
343         }
344
345         /**
346          * @see DBManager::getFieldsArray()
347          */
348         public function getFieldsArray($result, $make_lower_case=false)
349         {
350                 $field_array = array();
351
352                 if(empty($result))
353                         return 0;
354
355                 $fields = mysql_num_fields($result);
356                 for ($i=0; $i < $fields; $i++) {
357                         $meta = mysql_fetch_field($result, $i);
358                         if (!$meta)
359                                 return array();
360
361                         if($make_lower_case == true)
362                                 $meta->name = strtolower($meta->name);
363
364                         $field_array[] = $meta->name;
365                 }
366
367                 return $field_array;
368         }
369
370         /**
371          * @see DBManager::fetchRow()
372          */
373         public function fetchRow($result)
374         {
375                 if (empty($result))     return false;
376
377                 return mysql_fetch_assoc($result);
378         }
379
380         /**
381          * @see DBManager::getTablesArray()
382          */
383         public function getTablesArray()
384         {
385                 $this->log->debug('Fetching table list');
386
387                 if ($this->getDatabase()) {
388                         $tables = array();
389                         $r = $this->query('SHOW TABLES');
390                         if (!empty($r)) {
391                                 while ($a = $this->fetchByAssoc($r)) {
392                                         $row = array_values($a);
393                                         $tables[]=$row[0];
394                                 }
395                                 return $tables;
396                         }
397                 }
398
399                 return false; // no database available
400         }
401
402         /**
403          * @see DBManager::version()
404          */
405         public function version()
406         {
407                 return $this->getOne("SELECT version() version");
408         }
409
410         /**
411          * @see DBManager::tableExists()
412          */
413         public function tableExists($tableName)
414         {
415                 $this->log->info("tableExists: $tableName");
416
417                 if ($this->getDatabase()) {
418                         $result = $this->query("SHOW TABLES LIKE ".$this->quoted($tableName));
419                         if(empty($result)) return false;
420                         $row = $this->fetchByAssoc($result);
421                         return !empty($row);
422                 }
423
424                 return false;
425         }
426
427         /**
428          * Get tables like expression
429          * @param $like string
430          * @return array
431          */
432         public function tablesLike($like)
433         {
434                 if ($this->getDatabase()) {
435                         $tables = array();
436                         $r = $this->query('SHOW TABLES LIKE '.$this->quoted($like));
437                         if (!empty($r)) {
438                                 while ($a = $this->fetchByAssoc($r)) {
439                                         $row = array_values($a);
440                                         $tables[]=$row[0];
441                                 }
442                                 return $tables;
443                         }
444                 }
445                 return false;
446         }
447
448         /**
449          * @see DBManager::quote()
450          */
451         public function quote($string)
452         {
453                 if(is_array($string)) {
454                         return $this->arrayQuote($string);
455                 }
456                 return mysql_real_escape_string($this->quoteInternal($string), $this->getDatabase());
457         }
458
459         /**
460          * @see DBManager::connect()
461          */
462         public function connect(array $configOptions = null, $dieOnError = false)
463         {
464                 global $sugar_config;
465
466                 if(is_null($configOptions))
467                         $configOptions = $sugar_config['dbconfig'];
468
469                 if ($this->getOption('persistent')) {
470                         $this->database = @mysql_pconnect(
471                                 $configOptions['db_host_name'],
472                                 $configOptions['db_user_name'],
473                                 $configOptions['db_password']
474                                 );
475                 }
476
477                 if (!$this->database) {
478                         $this->database = mysql_connect(
479                                         $configOptions['db_host_name'],
480                                         $configOptions['db_user_name'],
481                                         $configOptions['db_password']
482                                         );
483                         if(empty($this->database)) {
484                                 $GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].":".mysql_error());
485                                 if($dieOnError) {
486                                         if(isset($GLOBALS['app_strings']['ERR_NO_DB'])) {
487                                                 sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
488                                         } else {
489                                                 sugar_die("Could not connect to the database. Please refer to sugarcrm.log for details.");
490                                         }
491                                 } else {
492                                         return false;
493                                 }
494                         }
495                         // Do not pass connection information because we have not connected yet
496                         if($this->database  && $this->getOption('persistent')){
497                                 $_SESSION['administrator_error'] = "<b>Severe Performance Degradation: Persistent Database Connections "
498                                         . "not working.  Please set \$sugar_config['dbconfigoption']['persistent'] to false "
499                                         . "in your config.php file</b>";
500                         }
501                 }
502                 if(!empty($configOptions['db_name']) && !@mysql_select_db($configOptions['db_name'])) {
503                         $GLOBALS['log']->fatal( "Unable to select database {$configOptions['db_name']}: " . mysql_error($this->database));
504                         if($dieOnError) {
505                                 sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
506                         } else {
507                                 return false;
508                         }
509                 }
510
511                 // cn: using direct calls to prevent this from spamming the Logs
512             mysql_query("SET CHARACTER SET utf8", $this->database);
513             $names = "SET NAMES 'utf8'";
514             $collation = $this->getOption('collation');
515             if(!empty($collation)) {
516                 $names .= " COLLATE '$collation'";
517                 }
518             mysql_query($names, $this->database);
519
520                 if(!$this->checkError('Could Not Connect:', $dieOnError))
521                         $GLOBALS['log']->info("connected to db");
522                 $this->connectOptions = $configOptions;
523
524                 $GLOBALS['log']->info("Connect:".$this->database);
525                 return true;
526         }
527
528         /**
529          * @see DBManager::repairTableParams()
530          *
531          * For MySQL, we can write the ALTER TABLE statement all in one line, which speeds things
532          * up quite a bit. So here, we'll parse the returned SQL into a single ALTER TABLE command.
533          */
534         public function repairTableParams($tablename, $fielddefs, $indices, $execute = true, $engine = null)
535         {
536                 $sql = parent::repairTableParams($tablename,$fielddefs,$indices,false,$engine);
537
538                 if ( $sql == '' )
539                         return '';
540
541                 if ( stristr($sql,'create table') )
542                 {
543                         if ($execute) {
544                                 $msg = "Error creating table: ".$tablename. ":";
545                                 $this->query($sql,true,$msg);
546                         }
547                         return $sql;
548                 }
549
550                 // first, parse out all the comments
551                 $match = array();
552                 preg_match_all('!/\*.*?\*/!is', $sql, $match);
553                 $commentBlocks = $match[0];
554                 $sql = preg_replace('!/\*.*?\*/!is','', $sql);
555
556                 // now, we should only have alter table statements
557                 // let's replace the 'alter table name' part with a comma
558                 $sql = preg_replace("!alter table $tablename!is",', ', $sql);
559
560                 // re-add it at the beginning
561                 $sql = substr_replace($sql,'',strpos($sql,','),1);
562                 $sql = str_replace(";","",$sql);
563                 $sql = str_replace("\n","",$sql);
564                 $sql = "ALTER TABLE $tablename $sql";
565
566                 if ( $execute )
567                         $this->query($sql,'Error with MySQL repair table');
568
569                 // and re-add the comments at the beginning
570                 $sql = implode("\n",$commentBlocks) . "\n". $sql . "\n";
571
572                 return $sql;
573         }
574
575         /**
576          * @see DBManager::convert()
577          */
578         public function convert($string, $type, array $additional_parameters = array())
579         {
580                 $all_parameters = $additional_parameters;
581                 if(is_array($string)) {
582                         $all_parameters = array_merge($string, $all_parameters);
583                 } elseif (!is_null($string)) {
584                         array_unshift($all_parameters, $string);
585                 }
586                 $all_strings = implode(',', $all_parameters);
587
588                 switch (strtolower($type)) {
589                         case 'today':
590                                 return "CURDATE()";
591                         case 'left':
592                                 return "LEFT($all_strings)";
593                         case 'date_format':
594                                 if(empty($additional_parameters)) {
595                                         return "DATE_FORMAT($string,'%Y-%m-%d')";
596                                 } else {
597                                         $format = $additional_parameters[0];
598                                         if($format[0] != "'") {
599                                                 $format = $this->quoted($format);
600                                         }
601                                         return "DATE_FORMAT($string,$format)";
602                                 }
603                         case 'ifnull':
604                                 if(empty($additional_parameters) && !strstr($all_strings, ",")) {
605                                         $all_strings .= ",''";
606                                 }
607                                 return "IFNULL($all_strings)";
608                         case 'concat':
609                                 return "CONCAT($all_strings)";
610                         case 'quarter':
611                                         return "QUARTER($string)";
612                         case "length":
613                                         return "LENGTH($string)";
614                         case 'month':
615                                         return "MONTH($string)";
616                         case 'add_date':
617                                         return "DATE_ADD($string, INTERVAL {$additional_parameters[0]} {$additional_parameters[1]})";
618                         case 'add_time':
619                                         return "DATE_ADD($string, INTERVAL + CONCAT({$additional_parameters[0]}, ':', {$additional_parameters[1]}) HOUR_MINUTE)";
620             case 'add_tz_offset' :
621                 $getUserUTCOffset = $GLOBALS['timedate']->getUserUTCOffset();
622                 $operation = $getUserUTCOffset < 0 ? '-' : '+';
623                 return $string . ' ' . $operation . ' INTERVAL ' . abs($getUserUTCOffset) . ' MINUTE';
624             case 'avg':
625                 return "avg($string)";
626                 }
627
628                 return $string;
629         }
630
631         /**
632          * (non-PHPdoc)
633          * @see DBManager::fromConvert()
634          */
635         public function fromConvert($string, $type)
636         {
637                 return $string;
638         }
639
640         /**
641          * Returns the name of the engine to use or null if we are to use the default
642          *
643          * @param  object $bean SugarBean instance
644          * @return string
645          */
646         protected function getEngine($bean)
647         {
648                 global $dictionary;
649                 $engine = null;
650                 if (isset($dictionary[$bean->getObjectName()]['engine'])) {
651                         $engine = $dictionary[$bean->getObjectName()]['engine'];
652                 }
653                 return $engine;
654         }
655
656         /**
657          * Returns true if the engine given is enabled in the backend
658          *
659          * @param  string $engine
660          * @return bool
661          */
662         protected function isEngineEnabled($engine)
663         {
664                 if(!is_string($engine)) return false;
665
666                 $engine = strtoupper($engine);
667
668                 $r = $this->query("SHOW ENGINES");
669
670                 while ( $row = $this->fetchByAssoc($r) )
671                         if ( strtoupper($row['Engine']) == $engine )
672                                 return ($row['Support']=='YES' || $row['Support']=='DEFAULT');
673
674                 return false;
675         }
676
677         /**
678          * @see DBManager::createTableSQL()
679          */
680         public function createTableSQL(SugarBean $bean)
681         {
682                 $tablename = $bean->getTableName();
683                 $fieldDefs = $bean->getFieldDefinitions();
684                 $indices   = $bean->getIndices();
685                 $engine    = $this->getEngine($bean);
686                 return $this->createTableSQLParams($tablename, $fieldDefs, $indices, $engine);
687         }
688
689         /**
690          * Generates sql for create table statement for a bean.
691          *
692          * @param  string $tablename
693          * @param  array  $fieldDefs
694          * @param  array  $indices
695          * @param  string $engine optional, MySQL engine to use
696          * @return string SQL Create Table statement
697         */
698         public function createTableSQLParams($tablename, $fieldDefs, $indices, $engine = null)
699         {
700                 if ( empty($engine) && isset($fieldDefs['engine']))
701                         $engine = $fieldDefs['engine'];
702                 if ( !$this->isEngineEnabled($engine) )
703                         $engine = '';
704
705                 $columns = $this->columnSQLRep($fieldDefs, false, $tablename);
706                 if (empty($columns))
707                         return false;
708
709                 $keys = $this->keysSQL($indices);
710                 if (!empty($keys))
711                         $keys = ",$keys";
712
713                 // cn: bug 9873 - module tables do not get created in utf8 with assoc collation
714                 $collation = $this->getOption('collation');
715                 if(empty($collation)) {
716                     $collation = 'utf8_general_ci';
717                 }
718                 $sql = "CREATE TABLE $tablename ($columns $keys) CHARACTER SET utf8 COLLATE $collation";
719
720                 if (!empty($engine))
721                         $sql.= " ENGINE=$engine";
722
723                 return $sql;
724         }
725
726     /**
727      * Does this type represent text (i.e., non-varchar) value?
728      * @param string $type
729      */
730     public function isTextType($type)
731     {
732         $type = $this->getColumnType(strtolower($type));
733         return in_array($type, array('blob','text','longblob', 'longtext'));
734     }
735
736         /**
737          * @see DBManager::oneColumnSQLRep()
738          */
739         protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
740         {
741                 // always return as array for post-processing
742                 $ref = parent::oneColumnSQLRep($fieldDef, $ignoreRequired, $table, true);
743
744                 if ( $ref['colType'] == 'int' && !empty($fieldDef['len']) ) {
745                         $ref['colType'] .= "(".$fieldDef['len'].")";
746                 }
747
748                 // bug 22338 - don't set a default value on text or blob fields
749                 if ( isset($ref['default']) &&
750             in_array($ref['colBaseType'], array('text', 'blob', 'longtext', 'longblob')))
751                             $ref['default'] = '';
752
753                 if ( $return_as_array )
754                         return $ref;
755                 else
756                         return "{$ref['name']} {$ref['colType']} {$ref['default']} {$ref['required']} {$ref['auto_increment']}";
757         }
758
759         /**
760          * @see DBManager::changeColumnSQL()
761          */
762         protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false)
763         {
764                 $columns = array();
765                 if ($this->isFieldArray($fieldDefs)){
766                         foreach ($fieldDefs as $def){
767                                 if ($action == 'drop')
768                                         $columns[] = $def['name'];
769                                 else
770                                         $columns[] = $this->oneColumnSQLRep($def, $ignoreRequired);
771                         }
772                 } else {
773                         if ($action == 'drop')
774                                 $columns[] = $fieldDefs['name'];
775                 else
776                         $columns[] = $this->oneColumnSQLRep($fieldDefs);
777                 }
778
779                 return "ALTER TABLE $tablename $action COLUMN ".implode(",$action column ", $columns);
780         }
781
782         /**
783          * Generates SQL for key specification inside CREATE TABLE statement
784          *
785          * The passes array is an array of field definitions or a field definition
786          * itself. The keys generated will be either primary, foreign, unique, index
787          * or none at all depending on the setting of the "key" parameter of a field definition
788          *
789          * @param  array  $indices
790          * @param  bool   $alter_table
791          * @param  string $alter_action
792          * @return string SQL Statement
793          */
794         protected function keysSQL($indices, $alter_table = false, $alter_action = '')
795         {
796         // check if the passed value is an array of fields.
797         // if not, convert it into an array
798         if (!$this->isFieldArray($indices))
799                 $indices[] = $indices;
800
801         $columns = array();
802         foreach ($indices as $index) {
803                 if(!empty($index['db']) && $index['db'] != $this->dbType)
804                         continue;
805                 if (isset($index['source']) && $index['source'] != 'db')
806                         continue;
807
808                 $type = $index['type'];
809                 $name = $index['name'];
810
811                 if (is_array($index['fields']))
812                         $fields = implode(", ", $index['fields']);
813                 else
814                         $fields = $index['fields'];
815
816                 switch ($type) {
817                 case 'unique':
818                         $columns[] = " UNIQUE $name ($fields)";
819                         break;
820                 case 'primary':
821                         $columns[] = " PRIMARY KEY ($fields)";
822                         break;
823                 case 'index':
824                 case 'foreign':
825                 case 'clustered':
826                 case 'alternate_key':
827                         /**
828                                 * @todo here it is assumed that the primary key of the foreign
829                                 * table will always be named 'id'. It must be noted though
830                                 * that this can easily be fixed by referring to db dictionary
831                                 * to find the correct primary field name
832                                 */
833                         if ( $alter_table )
834                                 $columns[] = " INDEX $name ($fields)";
835                         else
836                                 $columns[] = " KEY $name ($fields)";
837                         break;
838                 case 'fulltext':
839                         if ($this->full_text_indexing_installed())
840                                 $columns[] = " FULLTEXT ($fields)";
841                         else
842                                 $GLOBALS['log']->debug('MYISAM engine is not available/enabled, full-text indexes will be skipped. Skipping:',$name);
843                         break;
844                 }
845         }
846         $columns = implode(", $alter_action ", $columns);
847         if(!empty($alter_action)){
848                 $columns = $alter_action . ' '. $columns;
849         }
850         return $columns;
851         }
852
853         /**
854          * @see DBManager::setAutoIncrement()
855          */
856         protected function setAutoIncrement($table, $field_name)
857         {
858                 return "auto_increment";
859         }
860
861         /**
862          * Sets the next auto-increment value of a column to a specific value.
863          *
864          * @param  string $table tablename
865          * @param  string $field_name
866          */
867         public function setAutoIncrementStart($table, $field_name, $start_value)
868         {
869                 $start_value = (int)$start_value;
870                 return $this->query( "ALTER TABLE $table AUTO_INCREMENT = $start_value;");
871         }
872
873         /**
874          * Returns the next value for an auto increment
875          *
876          * @param  string $table tablename
877          * @param  string $field_name
878          * @return string
879          */
880         public function getAutoIncrement($table, $field_name)
881         {
882                 $result = $this->query("SHOW TABLE STATUS LIKE '$table'");
883                 $row = $this->fetchByAssoc($result);
884                 if (!empty($row['Auto_increment']))
885                         return $row['Auto_increment'];
886
887                 return "";
888         }
889
890         /**
891          * @see DBManager::get_indices()
892          */
893         public function get_indices($tablename)
894         {
895                 //find all unique indexes and primary keys.
896                 $result = $this->query("SHOW INDEX FROM $tablename");
897
898                 $indices = array();
899                 while (($row=$this->fetchByAssoc($result)) !=null) {
900                         $index_type='index';
901                         if ($row['Key_name'] =='PRIMARY') {
902                                 $index_type='primary';
903                         }
904                         elseif ( $row['Non_unique'] == '0' ) {
905                                 $index_type='unique';
906                         }
907                         $name = strtolower($row['Key_name']);
908                         $indices[$name]['name']=$name;
909                         $indices[$name]['type']=$index_type;
910                         $indices[$name]['fields'][]=strtolower($row['Column_name']);
911                 }
912                 return $indices;
913         }
914
915         /**
916          * @see DBManager::add_drop_constraint()
917          */
918         public function add_drop_constraint($table, $definition, $drop = false)
919         {
920                 $type         = $definition['type'];
921                 $fields       = implode(',',$definition['fields']);
922                 $name         = $definition['name'];
923                 $sql          = '';
924
925                 switch ($type){
926                 // generic indices
927                 case 'index':
928                 case 'alternate_key':
929                 case 'clustered':
930                         if ($drop)
931                                 $sql = "ALTER TABLE {$table} DROP INDEX {$name} ";
932                         else
933                                 $sql = "ALTER TABLE {$table} ADD INDEX {$name} ({$fields})";
934                         break;
935                 // constraints as indices
936                 case 'unique':
937                         if ($drop)
938                                 $sql = "ALTER TABLE {$table} DROP INDEX $name";
939                         else
940                                 $sql = "ALTER TABLE {$table} ADD CONSTRAINT UNIQUE {$name} ({$fields})";
941                         break;
942                 case 'primary':
943                         if ($drop)
944                                 $sql = "ALTER TABLE {$table} DROP PRIMARY KEY";
945                         else
946                                 $sql = "ALTER TABLE {$table} ADD CONSTRAINT PRIMARY KEY ({$fields})";
947                         break;
948                 case 'foreign':
949                         if ($drop)
950                                 $sql = "ALTER TABLE {$table} DROP FOREIGN KEY ({$fields})";
951                         else
952                                 $sql = "ALTER TABLE {$table} ADD CONSTRAINT FOREIGN KEY {$name} ({$fields}) REFERENCES {$definition['foreignTable']}({$definition['foreignField']})";
953                         break;
954                 }
955                 return $sql;
956         }
957
958         /**
959          * Runs a query and returns a single row
960          *
961          * @param  string   $sql        SQL Statement to execute
962          * @param  bool     $dieOnError True if we want to call die if the query returns errors
963          * @param  string   $msg        Message to log if error occurs
964          * @param  bool     $suppress   Message to log if error occurs
965          * @return array    single row from the query
966          */
967         public function fetchOne($sql, $dieOnError = false, $msg = '', $suppress = false)
968         {
969                 if(stripos($sql, ' LIMIT ') === false) {
970                         // little optimization to just fetch one row
971                         $sql .= " LIMIT 0,1";
972                 }
973                 return parent::fetchOne($sql, $dieOnError, $msg, $suppress);
974         }
975
976         /**
977          * @see DBManager::full_text_indexing_installed()
978          */
979         public function full_text_indexing_installed($dbname = null)
980         {
981                 return $this->isEngineEnabled('MyISAM');
982         }
983
984         /**
985          * @see DBManager::massageFieldDef()
986          */
987         public function massageFieldDef(&$fieldDef, $tablename)
988         {
989                 parent::massageFieldDef($fieldDef,$tablename);
990
991                 if ( isset($fieldDef['default']) &&
992                         ($fieldDef['dbType'] == 'text'
993                                 || $fieldDef['dbType'] == 'blob'
994                                 || $fieldDef['dbType'] == 'longtext'
995                                 || $fieldDef['dbType'] == 'longblob' ))
996                         unset($fieldDef['default']);
997                 if ($fieldDef['dbType'] == 'uint')
998                         $fieldDef['len'] = '10';
999                 if ($fieldDef['dbType'] == 'ulong')
1000                         $fieldDef['len'] = '20';
1001                 if ($fieldDef['dbType'] == 'bool')
1002                         $fieldDef['type'] = 'tinyint';
1003                 if ($fieldDef['dbType'] == 'bool' && empty($fieldDef['default']) )
1004                         $fieldDef['default'] = '0';
1005                 if (($fieldDef['dbType'] == 'varchar' || $fieldDef['dbType'] == 'enum') && empty($fieldDef['len']) )
1006                         $fieldDef['len'] = '255';
1007                 if ($fieldDef['dbType'] == 'uint')
1008                         $fieldDef['len'] = '10';
1009                 if ($fieldDef['dbType'] == 'int' && empty($fieldDef['len']) )
1010                         $fieldDef['len'] = '11';
1011
1012                 if($fieldDef['dbType'] == 'decimal') {
1013                         if(isset($fieldDef['len'])) {
1014                                 if(strstr($fieldDef['len'], ",") === false) {
1015                                         $fieldDef['len'] .= ",0";
1016                                 }
1017                         } else {
1018                                 $fieldDef['len']  = '10,0';
1019                         }
1020                 }
1021         }
1022
1023         /**
1024          * Generates SQL for dropping a table.
1025          *
1026          * @param  string $name table name
1027          * @return string SQL statement
1028          */
1029         public function dropTableNameSQL($name)
1030         {
1031                 return "DROP TABLE IF EXISTS ".$name;
1032         }
1033
1034         public function dropIndexes($tablename, $indexes, $execute = true)
1035         {
1036                 $sql = array();
1037                 foreach ($indexes as $index) {
1038                         $name =$index['name'];
1039                         if($execute) {
1040                         unset(self::$index_descriptions[$tablename][$name]);
1041                         }
1042                         if ($index['type'] == 'primary') {
1043                                 $sql[] = 'DROP PRIMARY KEY';
1044                         } else {
1045                                 $sql[] = "DROP INDEX $name";
1046                         }
1047                 }
1048                 if (!empty($sql)) {
1049             $sql = "ALTER TABLE $tablename " . join(",", $sql) . ";";
1050                         if($execute)
1051                                 $this->query($sql);
1052                 } else {
1053                         $sql = '';
1054                 }
1055                 return $sql;
1056         }
1057
1058         /**
1059          * List of available collation settings
1060          * @return string
1061          */
1062         public function getDefaultCollation()
1063         {
1064                 return "utf8_general_ci";
1065         }
1066
1067         /**
1068          * List of available collation settings
1069          * @return array
1070          */
1071         public function getCollationList()
1072         {
1073                 $q = "SHOW COLLATION LIKE 'utf8%'";
1074                 $r = $this->query($q);
1075                 $res = array();
1076                 while($a = $this->fetchByAssoc($r)) {
1077                         $res[] = $a['Collation'];
1078                 }
1079                 return $res;
1080         }
1081
1082         /**
1083          * (non-PHPdoc)
1084          * @see DBManager::renameColumnSQL()
1085          */
1086         public function renameColumnSQL($tablename, $column, $newname)
1087         {
1088                 $field = $this->describeField($column, $tablename);
1089                 $field['name'] = $newname;
1090                 return "ALTER TABLE $tablename CHANGE COLUMN $column ".$this->oneColumnSQLRep($field);
1091         }
1092
1093         public function emptyValue($type)
1094         {
1095                 $ctype = $this->getColumnType($type);
1096                 if($ctype == "datetime") {
1097                         return $this->convert($this->quoted("0000-00-00 00:00:00"), "datetime");
1098                 }
1099                 if($ctype == "date") {
1100                         return $this->convert($this->quoted("0000-00-00"), "date");
1101                 }
1102                 if($ctype == "time") {
1103                         return $this->convert($this->quoted("00:00:00"), "time");
1104                 }
1105                 return parent::emptyValue($type);
1106         }
1107
1108         /**
1109          * (non-PHPdoc)
1110          * @see DBManager::lastDbError()
1111          */
1112         public function lastDbError()
1113         {
1114                 if($this->database) {
1115                     if(mysql_errno($this->database)) {
1116                             return "MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database);
1117                     }
1118                 } else {
1119                         $err =  mysql_error();
1120                         if($err) {
1121                             return $err;
1122                         }
1123                 }
1124         return false;
1125     }
1126
1127         /**
1128          * Quote MySQL search term
1129          * @param unknown_type $term
1130          */
1131         protected function quoteTerm($term)
1132         {
1133                 if(strpos($term, ' ') !== false) {
1134                         return '"'.$term.'"';
1135                 }
1136                 return $term;
1137         }
1138
1139         /**
1140          * Generate fulltext query from set of terms
1141          * @param string $fields Field to search against
1142          * @param array $terms Search terms that may be or not be in the result
1143          * @param array $must_terms Search terms that have to be in the result
1144          * @param array $exclude_terms Search terms that have to be not in the result
1145          */
1146         public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array())
1147         {
1148                 $condition = array();
1149                 foreach($terms as $term) {
1150                         $condition[] = $this->quoteTerm($term);
1151                 }
1152                 foreach($must_terms as $term) {
1153                         $condition[] = "+".$this->quoteTerm($term);
1154                 }
1155                 foreach($exclude_terms as $term) {
1156                         $condition[] = "-".$this->quoteTerm($term);
1157                 }
1158                 $condition = $this->quoted(join(" ",$condition));
1159                 return "MATCH($field) AGAINST($condition IN BOOLEAN MODE)";
1160         }
1161
1162         /**
1163          * Get list of all defined charsets
1164          * @return array
1165          */
1166         protected function getCharsetInfo()
1167         {
1168                 $charsets = array();
1169                 $res = $this->query("show variables like 'character\\_set\\_%'");
1170                 while($row = $this->fetchByAssoc($res)) {
1171                         $charsets[$row['Variable_name']] = $row['Value'];
1172                 }
1173                 return $charsets;
1174         }
1175
1176         public function getDbInfo()
1177         {
1178                 $charsets = $this->getCharsetInfo();
1179                 $charset_str = array();
1180                 foreach($charsets as $name => $value) {
1181                         $charset_str[] = "$name = $value";
1182                 }
1183                 return array(
1184                         "MySQL Version" => @mysql_get_client_info(),
1185                         "MySQL Host Info" => @mysql_get_host_info($this->database),
1186                         "MySQL Server Info" => @mysql_get_server_info($this->database),
1187                         "MySQL Client Encoding" =>  @mysql_client_encoding($this->database),
1188                         "MySQL Character Set Settings" => join(", ", $charset_str),
1189                 );
1190         }
1191
1192         public function validateQuery($query)
1193         {
1194                 $res = $this->query("EXPLAIN $query");
1195                 return !empty($res);
1196         }
1197
1198         protected function makeTempTableCopy($table)
1199         {
1200                 $this->log->debug("creating temp table for [$table]...");
1201                 $result = $this->query("SHOW CREATE TABLE {$table}");
1202                 if(empty($result)) {
1203                         return false;
1204                 }
1205                 $row = $this->fetchByAssoc($result);
1206                 if(empty($row) || empty($row['Create Table'])) {
1207                     return false;
1208                 }
1209                 $create = $row['Create Table'];
1210                 // rewrite DDL with _temp name
1211                 $tempTableQuery = str_replace("CREATE TABLE `{$table}`", "CREATE TABLE `{$table}__uw_temp`", $create);
1212                 $r2 = $this->query($tempTableQuery);
1213                 if(empty($r2)) {
1214                         return false;
1215                 }
1216
1217                 // get sample data into the temp table to test for data/constraint conflicts
1218                 $this->log->debug('inserting temp dataset...');
1219                 $q3 = "INSERT INTO `{$table}__uw_temp` SELECT * FROM `{$table}` LIMIT 10";
1220                 $this->query($q3, false, "Preflight Failed for: {$q3}");
1221                 return true;
1222         }
1223
1224         /**
1225          * Tests an ALTER TABLE query
1226          * @param string table The table name to get DDL
1227          * @param string query The query to test.
1228          * @return string Non-empty if error found
1229          */
1230         protected function verifyAlterTable($table, $query)
1231         {
1232                 $this->log->debug("verifying ALTER TABLE");
1233                 // Skipping ALTER TABLE [table] DROP PRIMARY KEY because primary keys are not being copied
1234                 // over to the temp tables
1235                 if(strpos(strtoupper($query), 'DROP PRIMARY KEY') !== false) {
1236                         $this->log->debug("Skipping DROP PRIMARY KEY");
1237                         return '';
1238                 }
1239                 if(!$this->makeTempTableCopy($table)) {
1240                         return 'Could not create temp table copy';
1241                 }
1242
1243                 // test the query on the test table
1244                 $this->log->debug('testing query: ['.$query.']');
1245                 $tempTableTestQuery = str_replace("ALTER TABLE `{$table}`", "ALTER TABLE `{$table}__uw_temp`", $query);
1246                 if (strpos($tempTableTestQuery, 'idx') === false) {
1247                         if(strpos($tempTableTestQuery, '__uw_temp') === false) {
1248                                 return 'Could not use a temp table to test query!';
1249                         }
1250
1251                         $this->log->debug('testing query on temp table: ['.$tempTableTestQuery.']');
1252                         $this->query($tempTableTestQuery, false, "Preflight Failed for: {$query}");
1253                 } else {
1254                         // test insertion of an index on a table
1255                         $tempTableTestQuery_idx = str_replace("ADD INDEX `idx_", "ADD INDEX `temp_idx_", $tempTableTestQuery);
1256                         $this->log->debug('testing query on temp table: ['.$tempTableTestQuery_idx.']');
1257                         $this->query($tempTableTestQuery_idx, false, "Preflight Failed for: {$query}");
1258                 }
1259                 $mysqlError = $this->getL();
1260                 if(!empty($mysqlError)) {
1261                         return $mysqlError;
1262                 }
1263                 $this->dropTableName("{$table}__uw_temp");
1264
1265                 return '';
1266         }
1267
1268         protected function verifyGenericReplaceQuery($querytype, $table, $query)
1269         {
1270                 $this->log->debug("verifying $querytype statement");
1271
1272                 if(!$this->makeTempTableCopy($table)) {
1273                         return 'Could not create temp table copy';
1274                 }
1275                 // test the query on the test table
1276                 $this->log->debug('testing query: ['.$query.']');
1277                 $tempTableTestQuery = str_replace("$querytype `{$table}`", "$querytype `{$table}__uw_temp`", $query);
1278                 if(strpos($tempTableTestQuery, '__uw_temp') === false) {
1279                         return 'Could not use a temp table to test query!';
1280                 }
1281
1282                 $this->query($tempTableTestQuery, false, "Preflight Failed for: {$query}");
1283                 $error = $this->lastError(); // empty on no-errors
1284                 $this->dropTableName("{$table}__uw_temp"); // just in case
1285                 return $error;
1286         }
1287
1288         /**
1289          * Tests a DROP TABLE query
1290          * @param string table The table name to get DDL
1291          * @param string query The query to test.
1292          * @return string Non-empty if error found
1293          */
1294         public function verifyDropTable($table, $query)
1295         {
1296                 return $this->verifyGenericReplaceQuery("DROP TABLE", $table, $query);
1297         }
1298
1299         /**
1300          * Tests an INSERT INTO query
1301          * @param string table The table name to get DDL
1302          * @param string query The query to test.
1303          * @return string Non-empty if error found
1304          */
1305         public function verifyInsertInto($table, $query)
1306         {
1307                 return $this->verifyGenericReplaceQuery("INSERT INTO", $table, $query);
1308         }
1309
1310         /**
1311          * Tests an UPDATE query
1312          * @param string table The table name to get DDL
1313          * @param string query The query to test.
1314          * @return string Non-empty if error found
1315          */
1316         public function verifyUpdate($table, $query)
1317         {
1318                 return $this->verifyGenericReplaceQuery("UPDATE", $table, $query);
1319         }
1320
1321         /**
1322          * Tests an DELETE FROM query
1323          * @param string table The table name to get DDL
1324          * @param string query The query to test.
1325          * @return string Non-empty if error found
1326          */
1327         public function verifyDeleteFrom($table, $query)
1328         {
1329                 return $this->verifyGenericReplaceQuery("DELETE FROM", $table, $query);
1330         }
1331
1332         /**
1333          * Check if certain database exists
1334          * @param string $dbname
1335          */
1336         public function dbExists($dbname)
1337         {
1338                 $db = $this->getOne("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ".$this->quoted($dbname));
1339                 return !empty($db);
1340         }
1341
1342         /**
1343          * Select database
1344          * @param string $dbname
1345          */
1346         protected function selectDb($dbname)
1347         {
1348                 return mysql_select_db($dbname);
1349         }
1350
1351         /**
1352          * Check if certain DB user exists
1353          * @param string $username
1354          */
1355         public function userExists($username)
1356         {
1357                 $db = $this->getOne("SELECT DATABASE()");
1358                 if(!$this->selectDb("mysql")) {
1359                         return false;
1360                 }
1361                 $user = $this->getOne("select count(*) from user where user = ".$this->quoted($username));
1362                 if(!$this->selectDb($db)) {
1363                         $this->checkError("Cannot select database $db", true);
1364                 }
1365                 return !empty($user);
1366         }
1367
1368         /**
1369          * Create DB user
1370          * @param string $database_name
1371          * @param string $host_name
1372          * @param string $user
1373          * @param string $password
1374          */
1375         public function createDbUser($database_name, $host_name, $user, $password)
1376         {
1377                 $qpassword = $this->quote($password);
1378                 $this->query("GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, DROP, INDEX
1379                                                         ON `$database_name`.*
1380                                                         TO \"$user\"@\"$host_name\"
1381                                                         IDENTIFIED BY '{$qpassword}';", true);
1382
1383                 $this->query("SET PASSWORD FOR \"{$user}\"@\"{$host_name}\" = password('{$qpassword}');", true);
1384                 if($host_name != 'localhost') {
1385                         $this->createDbUser($database_name, "localhost", $user, $password);
1386                 }
1387         }
1388
1389         /**
1390          * Create a database
1391          * @param string $dbname
1392          */
1393         public function createDatabase($dbname)
1394         {
1395                 $this->query("CREATE DATABASE `$dbname` CHARACTER SET utf8 COLLATE utf8_general_ci", true);
1396         }
1397
1398         public function preInstall()
1399         {
1400                 $db->query("ALTER DATABASE `{$setup_db_database_name}` DEFAULT CHARACTER SET utf8", true);
1401                 $db->query("ALTER DATABASE `{$setup_db_database_name}` DEFAULT COLLATE utf8_general_ci", true);
1402
1403         }
1404
1405         /**
1406          * Drop a database
1407          * @param string $dbname
1408          */
1409         public function dropDatabase($dbname)
1410         {
1411                 return $this->query("DROP DATABASE IF EXISTS `$dbname`", true);
1412         }
1413
1414         /**
1415          * Check if this driver can be used
1416          * @return bool
1417          */
1418         public function valid()
1419         {
1420                 return function_exists("mysql_connect");
1421         }
1422
1423         /**
1424          * Check DB version
1425          * @see DBManager::canInstall()
1426          */
1427         public function canInstall()
1428         {
1429                 $db_version = $this->version();
1430                 if(empty($db_version)) {
1431                         return array('ERR_DB_VERSION_FAILURE');
1432                 }
1433                 if(version_compare($db_version, '4.1.2') < 0) {
1434                         return array('ERR_DB_MYSQL_VERSION', $db_version);
1435                 }
1436                 return true;
1437         }
1438
1439         public function installConfig()
1440         {
1441                 return array(
1442                         'LBL_DBCONFIG_MSG3' =>  array(
1443                                 "setup_db_database_name" => array("label" => 'LBL_DBCONF_DB_NAME', "required" => true),
1444                         ),
1445                         'LBL_DBCONFIG_MSG2' =>  array(
1446                                 "setup_db_host_name" => array("label" => 'LBL_DBCONF_HOST_NAME', "required" => true),
1447                         ),
1448                         'LBL_DBCONF_TITLE_USER_INFO' => array(),
1449                         'LBL_DBCONFIG_B_MSG1' => array(
1450                                 "setup_db_admin_user_name" => array("label" => 'LBL_DBCONF_DB_ADMIN_USER', "required" => true),
1451                                 "setup_db_admin_password" => array("label" => 'LBL_DBCONF_DB_ADMIN_PASSWORD', "type" => "password"),
1452                         )
1453                 );
1454         }
1455
1456         /**
1457          * Disable keys on the table
1458          * @abstract
1459          * @param string $tableName
1460          */
1461         public function disableKeys($tableName)
1462         {
1463             return $this->query('ALTER TABLE '.$tableName.' DISABLE KEYS');
1464         }
1465
1466         /**
1467          * Re-enable keys on the table
1468          * @abstract
1469          * @param string $tableName
1470          */
1471         public function enableKeys($tableName)
1472         {
1473             return $this->query('ALTER TABLE '.$tableName.' ENABLE KEYS');
1474         }
1475
1476     /**
1477      * Returns a DB specific FROM clause which can be used to select against functions.
1478      * Note that depending on the database that this may also be an empty string.
1479      * @return string
1480      */
1481     public function getFromDummyTable()
1482     {
1483         return '';
1484     }
1485
1486     /**
1487      * Returns a DB specific piece of SQL which will generate GUID (UUID)
1488      * This string can be used in dynamic SQL to do multiple inserts with a single query.
1489      * I.e. generate a unique Sugar id in a sub select of an insert statement.
1490      * @return string
1491      */
1492
1493         public function getGuidSQL()
1494     {
1495         return 'UUID()';
1496     }
1497 }