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