]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/database/MysqlManager.php
Release 6.3.1
[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 //Technically we can port all the functions in the latest bean to this file
92 // that is what PEAR is doing anyways.
93
94 class MysqlManager extends DBManager
95 {
96     /**
97      * @see DBManager::$dbType
98      */
99     public $dbType = 'mysql';
100
101     /**
102      * @see DBManager::$backendFunctions
103      */
104     protected $backendFunctions = array(
105         'free_result'        => 'mysql_free_result',
106         'close'              => 'mysql_close',
107         'row_count'          => 'mysql_num_rows',
108         'affected_row_count' => 'mysql_affected_rows',
109         );
110
111     /**
112      * @see DBManager::checkError()
113      */
114     public function checkError(
115         $msg = '',
116         $dieOnError = false
117         )
118     {
119         if (parent::checkError($msg, $dieOnError))
120             return true;
121
122         if (mysql_errno($this->getDatabase())) {
123             if ($this->dieOnError || $dieOnError){
124                 $GLOBALS['log']->fatal("MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database));
125                 sugar_die ($GLOBALS['app_strings']['ERR_DB_FAIL']);
126             }
127             else {
128                 $this->last_error = $msg."MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database);
129                 $GLOBALS['log']->error("MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database));
130
131             }
132             return true;
133         }
134         return false;
135     }
136
137     /**
138      * Parses and runs queries
139      *
140      * @param  string   $sql        SQL Statement to execute
141      * @param  bool     $dieOnError True if we want to call die if the query returns errors
142      * @param  string   $msg        Message to log if error occurs
143      * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
144      * @param  bool     $autofree   True if we want to push this result into the $lastResult array.
145      * @return resource result set
146      */
147     public function query(
148         $sql,
149         $dieOnError = false,
150         $msg = '',
151         $suppress = false,
152         $autofree = false
153         )
154     {
155         parent::countQuery($sql);
156         $GLOBALS['log']->info('Query:' . $sql);
157         $this->checkConnection();
158         //$this->freeResult();
159         $this->query_time = microtime(true);
160         $this->lastsql = $sql;
161         if ($suppress==true) {
162         }
163         else {
164             $result = mysql_query($sql, $this->database);
165         }
166
167         $this->lastmysqlrow = -1;
168         $this->query_time = microtime(true) - $this->query_time;
169         $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
170
171
172         $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
173         if($autofree)
174             $this->lastResult[] =& $result;
175
176         return $result;
177     }
178
179     /**
180      * @see DBManager::limitQuery()
181      */
182     public function limitQuery(
183         $sql,
184         $start,
185         $count,
186         $dieOnError = false,
187         $msg = '')
188     {
189         $start = (int)$start;
190         $count = (int)$count;
191         if ($start < 0)
192             $start = 0;
193         $GLOBALS['log']->debug('Limit Query:' . $sql. ' Start: ' .$start . ' count: ' . $count);
194
195         $sql = "$sql LIMIT $start,$count";
196         $this->lastsql = $sql;
197
198         if(!empty($GLOBALS['sugar_config']['check_query'])){
199             $this->checkQuery($sql);
200         }
201
202         return $this->query($sql, $dieOnError, $msg);
203     }
204
205
206     /**
207      * @see DBManager::checkQuery()
208      */
209     protected function checkQuery(
210         $sql
211         )
212     {
213         $result   = $this->query('EXPLAIN ' . $sql);
214         $badQuery = array();
215         while ($row = $this->fetchByAssoc($result)) {
216             if (empty($row['table']))
217                 continue;
218             $badQuery[$row['table']] = '';
219             if (strtoupper($row['type']) == 'ALL')
220                 $badQuery[$row['table']]  .=  ' Full Table Scan;';
221             if (empty($row['key']))
222                 $badQuery[$row['table']] .= ' No Index Key Used;';
223             if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using filesort') > 0)
224                 $badQuery[$row['table']] .= ' Using FileSort;';
225             if (!empty($row['Extra']) && substr_count($row['Extra'], 'Using temporary') > 0)
226                 $badQuery[$row['table']] .= ' Using Temporary Table;';
227         }
228
229         if ( empty($badQuery) )
230             return true;
231
232         foreach($badQuery as $table=>$data ){
233             if(!empty($data)){
234                 $warning = ' Table:' . $table . ' Data:' . $data;
235                 if(!empty($GLOBALS['sugar_config']['check_query_log'])){
236                     $GLOBALS['log']->fatal($sql);
237                     $GLOBALS['log']->fatal('CHECK QUERY:' .$warning);
238                 }
239                 else{
240                     $GLOBALS['log']->warn('CHECK QUERY:' .$warning);
241                 }
242             }
243         }
244
245         return false;
246     }
247
248     /**
249      * @see DBManager::describeField()
250      */
251     protected function describeField(
252         $name,
253         $tablename
254         )
255     {
256         global $table_descriptions;
257         if(isset($table_descriptions[$tablename])
258                 && isset($table_descriptions[$tablename][$name]))
259             return      $table_descriptions[$tablename][$name];
260
261         $table_descriptions[$tablename] = array();
262         $sql = "DESCRIBE $tablename";
263         $result = $this->query($sql);
264         while ($row = $this->fetchByAssoc($result) ){
265             $table_descriptions[$tablename][$row['Field']] = $row;
266             if(empty($table_descriptions[$tablename][$row['Field']]['Null']))
267                 $table_descriptions[$tablename][$row['Field']]['Null'] = 'NO';
268         }
269         if(isset($table_descriptions[$tablename][$name]))
270             return      $table_descriptions[$tablename][$name];
271
272         return array();
273     }
274
275     /**
276      * @see DBManager::getFieldsArray()
277      */
278     public function getFieldsArray(
279         &$result,
280         $make_lower_case=false)
281     {
282         $field_array = array();
283
284         if(! isset($result) || empty($result))
285             return 0;
286
287         $i = 0;
288         while ($i < mysql_num_fields($result)) {
289             $meta = mysql_fetch_field($result, $i);
290             if (!$meta)
291                 return 0;
292
293             if($make_lower_case == true)
294                 $meta->name = strtolower($meta->name);
295
296             $field_array[] = $meta->name;
297             $i++;
298         }
299
300         return $field_array;
301     }
302
303
304
305     /**
306      * @see DBManager::fetchByAssoc()
307      */
308     public function fetchByAssoc(
309         &$result,
310         $rowNum = -1,
311         $encode = true
312         )
313     {
314         if (!$result)
315             return false;
316
317         if ($result && $rowNum > -1){
318             if ($this->getRowCount($result) > $rowNum)
319                 mysql_data_seek($result, $rowNum);
320             $this->lastmysqlrow = $rowNum;
321         }
322
323         $row = mysql_fetch_assoc($result);
324
325         if ($encode && $this->encode && is_array($row))
326             return array_map('to_html', $row);
327
328         return $row;
329     }
330
331     /**
332      * @see DBManager::getTablesArray()
333      */
334     public function getTablesArray()
335     {
336         global $sugar_config;
337         $GLOBALS['log']->debug('Fetching table list');
338
339         if ($this->getDatabase()) {
340             $tables = array();
341             $r = $this->query('SHOW TABLES');
342             if (is_resource($r) || $r instanceOf mysqli_result ) {
343                 while ($a = $this->fetchByAssoc($r)) {
344                     $row = array_values($a);
345                                         $tables[]=$row[0];
346                 }
347                 return $tables;
348             }
349         }
350
351         return false; // no database available
352     }
353
354     /**
355      * @see DBManager::version()
356      */
357     public function version()
358     {
359         return $this->getOne("SELECT version() version");
360     }
361
362     /**
363      * @see DBManager::tableExists()
364      */
365     public function tableExists(
366         $tableName
367         )
368     {
369         $GLOBALS['log']->info("tableExists: $tableName");
370
371         if ($this->getDatabase()) {
372             $result = $this->query("SHOW TABLES LIKE '".$tableName."'");
373             return ($this->getRowCount($result) == 0) ? false : true;
374         }
375
376         return false;
377     }
378
379     /**
380      * @see DBManager::quote()
381      */
382     public function quote(
383         $string,
384         $isLike = true
385         )
386     {
387         return mysql_real_escape_string(parent::quote($string), $this->getDatabase());
388     }
389
390     /**
391      * @see DBManager::quoteForEmail()
392      */
393     public function quoteForEmail(
394         $string,
395         $isLike = true
396         )
397     {
398         return mysql_real_escape_string($string, $this->getDatabase());
399     }
400
401     /**
402      * @see DBManager::connect()
403      */
404         public function connect(
405         array $configOptions = null,
406         $dieOnError = false
407         )
408     {
409                 global $sugar_config;
410
411         if(is_null($configOptions))
412                         $configOptions = $sugar_config['dbconfig'];
413
414         if ($sugar_config['dbconfigoption']['persistent'] == true) {
415             $this->database = @mysql_pconnect(
416                 $configOptions['db_host_name'],
417                 $configOptions['db_user_name'],
418                 $configOptions['db_password']
419                 );
420         }
421
422         if (!$this->database) {
423             $this->database = mysql_connect(
424                     $configOptions['db_host_name'],
425                     $configOptions['db_user_name'],
426                     $configOptions['db_password']
427                     );
428             if(empty($this->database)) {
429                 $GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].":".mysql_error());
430                 sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
431             }
432             // Do not pass connection information because we have not connected yet
433             if($this->database  && $sugar_config['dbconfigoption']['persistent'] == true){
434                 $_SESSION['administrator_error'] = "<b>Severe Performance Degradation: Persistent Database Connections "
435                     . "not working.  Please set \$sugar_config['dbconfigoption']['persistent'] to false "
436                     . "in your config.php file</b>";
437             }
438         }
439         if(!@mysql_select_db($configOptions['db_name'])) {
440             $GLOBALS['log']->fatal( "Unable to select database {$configOptions['db_name']}: " . mysql_error($this->database));
441             sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
442         }
443
444         // cn: using direct calls to prevent this from spamming the Logs
445         $charset = "SET CHARACTER SET utf8";
446         if(isset($sugar_config['dbconfigoption']['collation']) && !empty($sugar_config['dbconfigoption']['collation']))
447                 $charset .= " COLLATE {$sugar_config['dbconfigoption']['collation']}";
448         mysql_query($charset, $this->database); // no quotes around "[charset]"
449         mysql_query("SET NAMES 'utf8'", $this->database);
450
451         if($this->checkError('Could Not Connect:', $dieOnError))
452             $GLOBALS['log']->info("connected to db");
453
454         $GLOBALS['log']->info("Connect:".$this->database);
455     }
456
457     /**
458      * @see DBManager::repairTableParams()
459      *
460      * For MySQL, we can write the ALTER TABLE statement all in one line, which speeds things
461      * up quite a bit. So here, we'll parse the returned SQL into a single ALTER TABLE command.
462      */
463     public function repairTableParams(
464         $tablename,
465         $fielddefs,
466         $indices,
467         $execute = true,
468         $engine = null
469         )
470     {
471         $sql = parent::repairTableParams($tablename,$fielddefs,$indices,false,$engine);
472
473         if ( $sql == '' )
474             return '';
475
476         if ( stristr($sql,'create table') )
477         {
478             if ($execute) {
479                 $msg = "Error creating table: ".$tablename. ":";
480                 $this->query($sql,true,$msg);
481                 }
482             return $sql;
483         }
484
485         // first, parse out all the comments
486         $match = array();
487         preg_match_all("!/\*.*?\*/!is", $sql, $match);
488         $commentBlocks = $match[0];
489         $sql = preg_replace("!/\*.*?\*/!is",'', $sql);
490
491         // now, we should only have alter table statements
492         // let's replace the 'alter table name' part with a comma
493         $sql = preg_replace("!alter table $tablename!is",', ', $sql);
494
495         // re-add it at the beginning
496         $sql = substr_replace($sql,'',strpos($sql,','),1);
497         $sql = str_replace(";","",$sql);
498         $sql = str_replace("\n","",$sql);
499         $sql = "ALTER TABLE $tablename $sql";
500
501         if ( $execute )
502             $this->query($sql,'Error with MySQL repair table');
503
504         // and re-add the comments at the beginning
505         $sql = implode("\n",$commentBlocks) . "\n". $sql . "\n";
506
507         return $sql;
508     }
509
510     /**
511      * @see DBManager::convert()
512      */
513     public function convert(
514         $string,
515         $type,
516         array $additional_parameters = array(),
517         array $additional_parameters_oracle_only = array()
518         )
519     {
520         // convert the parameters array into a comma delimited string
521         $additional_parameters_string = '';
522         if (!empty($additional_parameters))
523             $additional_parameters_string = ','.implode(',',$additional_parameters);
524
525         switch ($type) {
526         case 'today': return "CURDATE()";
527         case 'left': return "LEFT($string".$additional_parameters_string.")";
528         case 'date_format': return "DATE_FORMAT($string".$additional_parameters_string.")";
529         case 'datetime': return "DATE_FORMAT($string, '%Y-%m-%d %H:%i:%s')";
530         case 'IFNULL': return "IFNULL($string".$additional_parameters_string.")";
531         case 'CONCAT': return "CONCAT($string,".implode(",",$additional_parameters).")";
532         case 'text2char': return "$string";
533         }
534
535         return "$string";
536     }
537
538     /**
539      * @see DBManager::concat()
540      */
541     public function concat(
542         $table,
543         array $fields
544         )
545     {
546         $ret = '';
547
548         foreach ( $fields as $index => $field )
549             if (empty($ret))
550                 $ret = "CONCAT(". db_convert($table.".".$field,'IFNULL', array("''"));
551             else
552                 $ret.=  ",' ',".db_convert($table.".".$field,'IFNULL', array("''"));
553
554                 if (!empty($ret)) {
555                     $ret = "TRIM($ret))";
556                 }
557
558                 return $ret;
559     }
560 }