]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/database/DBManager.php
Release 6.5.0beta5
[Github/sugarcrm.git] / include / database / DBManager.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-2012 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  * Base database driver implementation
93  * @api
94  */
95 abstract class DBManager
96 {
97         /**
98          * Name of database
99          * @var resource
100          */
101         public $database = null;
102
103         /**
104          * Indicates whether we should die when we get an error from the DB
105          */
106         protected $dieOnError = false;
107
108         /**
109          * Indicates whether we should html encode the results from a query by default
110          */
111         protected $encode = true;
112
113         /**
114          * Records the execution time of the last query
115          */
116         protected $query_time = 0;
117
118         /**
119          * Last error message from the DB backend
120          */
121         protected $last_error = false;
122
123         /**
124          * Registry of available result sets
125          */
126         protected $lastResult;
127
128         /**
129          * Current query count
130          */
131         private static $queryCount = 0;
132
133         /**
134          * Query threshold limit
135          */
136         private static $queryLimit = 0;
137
138         /**
139          * Array of prepared statements and their correspoding parsed tokens
140          */
141         protected $preparedTokens = array();
142
143         /**
144          * TimeDate instance
145          * @var TimeDate
146          */
147         protected $timedate;
148
149         /**
150          * PHP Logger
151          * @var Logger
152          */
153         protected $log;
154
155         /**
156          * Table descriptions
157          * @var array
158          */
159         protected static $table_descriptions = array();
160
161         /**
162          * Index descriptions
163          * @var array
164          */
165         protected static $index_descriptions = array();
166
167         /**
168          * Maximum length of identifiers
169          * @abstract
170          * @var array
171          */
172         protected $maxNameLengths = array(
173                 'table' => 64,
174                 'column' => 64,
175                 'index' => 64,
176                 'alias' => 64
177         );
178
179         /**
180          * DB driver priority
181          * Higher priority drivers override lower priority ones
182          * @var int
183          */
184         public $priority = 0;
185
186         /**
187          * Driver name label, for install
188          * @absrtact
189          * @var string
190          */
191         public $label = '';
192
193         /**
194          * Type names map
195          * @abstract
196          * @var array
197          */
198         protected $type_map = array();
199
200         /**
201          * Type classification into:
202          * - int
203          * - bool
204          * - float
205          * - date
206          * @abstract
207          * @var array
208          */
209         protected $type_class = array(
210                         'int'      => 'int',
211                         'double'   => 'float',
212                         'float'    => 'float',
213                         'uint'     => 'int',
214                         'ulong'    => 'bigint',
215                         'long'     => 'bigint',
216                         'short'    => 'int',
217                         'date'     => 'date',
218                         'datetime' => 'date',
219                         'datetimecombo' => 'date',
220                         'time'     => 'time',
221                         'bool'     => 'bool',
222                         'tinyint'  => 'int',
223                         'currency' => 'float',
224                         'decimal'  => 'float',
225                         'decimal2' => 'float',
226         );
227
228         /**
229          * Capabilities this DB supports. Supported list:
230          * affected_rows        Can report query affected rows for UPDATE/DELETE
231          *                                      implement getAffectedRowCount()
232          * select_rows          Can report row count for SELECT
233          *                                      implement getRowCount()
234          * case_sensitive       Supports case-sensitive text columns
235          * fulltext                     Supports fulltext search indexes
236          * inline_keys          Supports defining keys together with the table
237          * auto_increment_sequence Autoincrement support implemented as sequence
238          * limit_subquery   Supports LIMIT clauses in subqueries
239          * create_user          Can create users for Sugar
240          * create_db            Can create databases
241          * collation            Supports setting collations
242          * disable_keys     Supports temporarily disabling keys (for upgrades, etc.)
243          *
244          * @abstract
245          * Special cases:
246          * fix:expandDatabase - needs expandDatabase fix, see expandDatabase.php
247          * TODO: verify if we need these cases
248          */
249         protected $capabilities = array();
250
251         /**
252          * Database options
253          * @var array
254          */
255         protected $options = array();
256
257     /**
258      * Create DB Driver
259      */
260         public function __construct()
261         {
262                 $this->timedate = TimeDate::getInstance();
263                 $this->log = $GLOBALS['log'];
264                 $this->helper = $this; // compatibility
265         }
266
267     /**
268      * Wrapper for those trying to access the private and protected class members directly
269      * @param string $p var name
270      * @return mixed
271      */
272         public function __get($p)
273         {
274                 $this->log->info('Call to DBManager::$'.$p.' is deprecated');
275                 return $this->$p;
276         }
277
278         /**
279          * Returns the current database handle
280          * @return resource
281          */
282         public function getDatabase()
283         {
284                 $this->checkConnection();
285                 return $this->database;
286         }
287
288         /**
289          * Returns this instance's DBHelper
290          * Actually now returns $this
291          * @deprecated
292          * @return DBManager
293          */
294         public function getHelper()
295         {
296                 return $this;
297         }
298
299         /**
300          * Checks for error happening in the database
301          *
302          * @param  string $msg        message to prepend to the error message
303          * @param  bool   $dieOnError true if we want to die immediately on error
304          * @return bool True if there was an error
305          */
306         public function checkError($msg = '', $dieOnError = false)
307         {
308                 if (empty($this->database)) {
309                         $this->registerError("$msg: Database Is Not Connected", $dieOnError);
310                         return true;
311                 }
312
313                 $dberror = $this->lastDbError();
314                 if($dberror === false) {
315                 $this->last_error = false;
316                 return false;
317                 }
318                 $this->registerError($msg, $dberror, $dieOnError);
319         return true;
320         }
321
322         /**
323          * Register database error
324          * If die-on-error flag is set, logs the message and dies,
325          * otherwise sets last_error to the message
326          * @param string $userMessage Message from function user
327          * @param string $message Message from SQL driver
328          * @param bool $dieOnError
329          */
330         protected function registerError($userMessage, $message, $dieOnError = false)
331         {
332                 if(!empty($message)) {
333                         if(!empty($userMessage)) {
334                                 $message = "$userMessage: $message";
335                         }
336                         if(empty($message)) {
337                             $message = "Database error";
338                         }
339                         $this->log->fatal($message);
340                         if ($dieOnError || $this->dieOnError) {
341                                 if(isset($GLOBALS['app_strings']['ERR_DB_FAIL'])) {
342                                         sugar_die($GLOBALS['app_strings']['ERR_DB_FAIL']);
343                                 } else {
344                                         sugar_die("Database error. Please check sugarcrm.log for details.");
345                                 }
346                         } else {
347                                 $this->last_error = $message;
348                         }
349                 }
350         }
351
352         /**
353          * Return DB error message for the last query executed
354          * @return string Last error message
355          */
356         public function lastError()
357         {
358                 return $this->last_error;
359         }
360
361         /**
362          * This method is called by every method that runs a query.
363          * If slow query dumping is turned on and the query time is beyond
364          * the time limit, we will log the query. This function may do
365          * additional reporting or log in a different area in the future.
366          *
367          * @param  string  $query query to log
368          * @return boolean true if the query was logged, false otherwise
369          */
370         protected function dump_slow_queries($query)
371         {
372                 global $sugar_config;
373
374                 $do_the_dump = isset($sugar_config['dump_slow_queries'])
375                         ? $sugar_config['dump_slow_queries'] : false;
376                 $slow_query_time_msec = isset($sugar_config['slow_query_time_msec'])
377                         ? $sugar_config['slow_query_time_msec'] : 5000;
378
379                 if($do_the_dump) {
380                         if($slow_query_time_msec < ($this->query_time * 1000)) {
381                                 // Then log both the query and the query time
382                                 $this->log->fatal('Slow Query (time:'.$this->query_time."\n".$query);
383                                 return true;
384                         }
385                 }
386                 return false;
387         }
388
389 /**
390         * Scans order by to ensure that any field being ordered by is.
391         *
392         * It will throw a warning error to the log file - fatal if slow query logging is enabled
393         *
394         * @param  string $sql         query to be run
395         * @param  bool   $object_name optional, object to look up indices in
396         * @return bool   true if an index is found false otherwise
397         */
398 protected function checkQuery($sql, $object_name = false)
399 {
400         $match = array();
401         preg_match_all("'.* FROM ([^ ]*).* ORDER BY (.*)'is", $sql, $match);
402         $indices = false;
403         if (!empty($match[1][0]))
404                 $table = $match[1][0];
405         else
406                 return false;
407
408         if (!empty($object_name) && !empty($GLOBALS['dictionary'][$object_name]))
409                 $indices = $GLOBALS['dictionary'][$object_name]['indices'];
410
411         if (empty($indices)) {
412                 foreach ( $GLOBALS['dictionary'] as $current ) {
413                         if ($current['table'] == $table){
414                                 $indices = $current['indices'];
415                                 break;
416                         }
417                 }
418         }
419         if (empty($indices)) {
420                 $this->log->warn('CHECK QUERY: Could not find index definitions for table ' . $table);
421                 return false;
422         }
423         if (!empty($match[2][0])) {
424                 $orderBys = explode(' ', $match[2][0]);
425                 foreach ($orderBys as $orderBy){
426                         $orderBy = trim($orderBy);
427                         if (empty($orderBy))
428                                 continue;
429                         $orderBy = strtolower($orderBy);
430                         if ($orderBy == 'asc' || $orderBy == 'desc')
431                                 continue;
432
433                         $orderBy = str_replace(array($table . '.', ','), '', $orderBy);
434
435                         foreach ($indices as $index)
436                                 if (empty($index['db']) || $index['db'] == $this->dbType)
437                                         foreach ($index['fields'] as $field)
438                                                 if ($field == $orderBy)
439                                                         return true;
440
441                         $warning = 'Missing Index For Order By Table: ' . $table . ' Order By:' . $orderBy ;
442                         if (!empty($GLOBALS['sugar_config']['dump_slow_queries']))
443                                 $this->log->fatal('CHECK QUERY:' .$warning);
444                         else
445                                 $this->log->warn('CHECK QUERY:' .$warning);
446                 }
447         }
448         return false;
449         }
450
451         /**
452          * Returns the time the last query took to execute
453          *
454          * @return int
455          */
456         public function getQueryTime()
457         {
458                 return $this->query_time;
459         }
460
461         /**
462          * Checks the current connection; if it is not connected then reconnect
463          */
464         public function checkConnection()
465         {
466                 $this->last_error = '';
467                 if (!isset($this->database))
468                         $this->connect();
469         }
470
471         /**
472          * Sets the dieOnError value
473          *
474          * @param bool $value
475          */
476         public function setDieOnError($value)
477         {
478                 $this->dieOnError = $value;
479         }
480
481     /**
482      * Implements a generic insert for any bean.
483      *
484      * @param SugarBean $bean SugarBean instance
485      * @return bool query result
486      *
487      */
488         public function insert(SugarBean $bean)
489         {
490                 $sql = $this->insertSQL($bean);
491                 $tablename =  $bean->getTableName();
492                 $msg = "Error inserting into table: $tablename:";
493                 return $this->query($sql,true,$msg);
494         }
495
496         /**
497          * Insert data into table by parameter definition
498          * @param string $table Table name
499          * @param array $field_defs Definitions in vardef-like format
500          * @param array $data Key/value to insert
501          * @param array $field_map Fields map from SugarBean
502          * @param bool $execute Execute or return query?
503      * @return bool query result
504      */
505         public function insertParams($table, $field_defs, $data, $field_map = null, $execute = true)
506         {
507                 $values = array();
508                 foreach ($field_defs as $field => $fieldDef)
509                 {
510                         if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')  continue;
511                         //custom fields handle there save seperatley
512                         if(!empty($field_map) && !empty($field_map[$field]['custom_type'])) continue;
513
514                         if(isset($data[$field])) {
515                                 // clean the incoming value..
516                                 $val = from_html($data[$field]);
517                         } else {
518                                 if(isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
519                                         $val = $fieldDef['default'];
520                                 } else {
521                                         $val = null;
522                                 }
523                         }
524
525                         //handle auto increment values here - we may have to do something like nextval for oracle
526                         if (!empty($fieldDef['auto_increment'])) {
527                                 $auto = $this->getAutoIncrementSQL($table, $fieldDef['name']);
528                                 if(!empty($auto)) {
529                                         $values[$field] = $auto;
530                                 }
531                         } elseif ($fieldDef['name'] == 'deleted') {
532                                 $values['deleted'] = (int)$val;
533                         } else {
534                                 // need to do some thing about types of values
535                                 if(!is_null($val) || !empty($fieldDef['required'])) {
536                                         $values[$field] = $this->massageValue($val, $fieldDef);
537                                 }
538                         }
539                 }
540
541                 if (empty($values))
542                         return $execute?true:''; // no columns set
543
544                 // get the entire sql
545                 $query = "INSERT INTO $table (".implode(",", array_keys($values)).")
546                                         VALUES (".implode(",", $values).")";
547                 return $execute?$this->query($query):$query;
548         }
549
550     /**
551      * Implements a generic update for any bean
552      *
553      * @param SugarBean $bean Sugarbean instance
554      * @param array $where values with the keys as names of fields.
555      * If we want to pass multiple values for a name, pass it as an array
556      * If where is not passed, it defaults to id of table
557      * @return bool query result
558      *
559      */
560         public function update(SugarBean $bean, array $where = array())
561         {
562                 $sql = $this->updateSQL($bean, $where);
563                 $tablename = $bean->getTableName();
564                 $msg = "Error updating table: $tablename:";
565                 return $this->query($sql,true,$msg);
566         }
567
568     /**
569      * Implements a generic delete for any bean identified by id
570      *
571      * @param SugarBean $bean Sugarbean instance
572      * @param array  $where values with the keys as names of fields.
573      * If we want to pass multiple values for a name, pass it as an array
574      * If where is not passed, it defaults to id of table
575      * @return bool query result
576      */
577         public function delete(SugarBean $bean, array $where = array())
578         {
579                 $sql = $this->deleteSQL($bean, $where);
580                 $tableName = $bean->getTableName();
581                 $msg = "Error deleting from table: ".$tableName. ":";
582                 return $this->query($sql,true,$msg);
583         }
584
585         /**
586          * Implements a generic retrieve for any bean identified by id
587          *
588          * If we want to pass multiple values for a name, pass it as an array
589          * If where is not passed, it defaults to id of table
590          *
591          * @param  SugarBean   $bean  Sugarbean instance
592          * @param  array    $where values with the keys as names of fields.
593          * @return resource result from the query
594          */
595         public function retrieve(SugarBean $bean, array $where = array())
596         {
597                 $sql = $this->retrieveSQL($bean, $where);
598                 $tableName = $bean->getTableName();
599                 $msg = "Error retriving values from table:".$tableName. ":";
600                 return $this->query($sql,true,$msg);
601         }
602
603         /**
604          * Implements a generic retrieve for a collection of beans.
605          *
606          * These beans will be joined in the sql by the key attribute of field defs.
607          * Currently, this function does support outer joins.
608          *
609          * @param  array $beans Sugarbean instance(s)
610          * @param  array $cols  columns to be returned with the keys as names of bean as identified by
611          * get_class of bean. Values of this array is the array of fieldDefs to be returned for a bean.
612          * If an empty array is passed, all columns are selected.
613          * @param  array $where  values with the keys as names of bean as identified by get_class of bean
614          * Each value at the first level is an array of values for that bean identified by name of fields.
615          * If we want to pass multiple values for a name, pass it as an array
616          * If where is not passed, all the rows will be returned.
617          * @return resource
618          */
619         public function retrieveView(array $beans, array $cols = array(), array $where = array())
620         {
621                 $sql = $this->retrieveViewSQL($beans, $cols, $where);
622                 $msg = "Error retriving values from View Collection:";
623                 return $this->query($sql,true,$msg);
624         }
625
626
627         /**
628          * Implements creation of a db table for a bean.
629          *
630          * NOTE: does not handle out-of-table constraints, use createConstraintSQL for that
631          * @param SugarBean $bean  Sugarbean instance
632          */
633         public function createTable(SugarBean $bean)
634         {
635                 $sql = $this->createTableSQL($bean);
636                 $tablename = $bean->getTableName();
637                 $msg = "Error creating table: $tablename:";
638                 $this->query($sql,true,$msg);
639                 if(!$this->supports("inline_keys")) {
640                 // handle constraints and indices
641                         $indicesArr = $this->createConstraintSql($bean);
642                         if (count($indicesArr) > 0)
643                                 foreach ($indicesArr as $indexSql)
644                                         $this->query($indexSql, true, $msg);
645                 }
646         }
647
648         /**
649          * returns SQL to create constraints or indices
650          *
651          * @param  SugarBean $bean SugarBean instance
652          * @return array list of SQL statements
653          */
654         protected function createConstraintSql(SugarBean $bean)
655         {
656                 return $this->getConstraintSql($bean->getIndices(), $bean->getTableName());
657         }
658
659         /**
660          * Implements creation of a db table
661          *
662          * @param string $tablename
663          * @param array  $fieldDefs  Field definitions, in vardef format
664          * @param array  $indices    Index definitions, in vardef format
665          * @param string $engine    Engine parameter, used for MySQL engine so far
666      * @todo: refactor engine param to be more generic
667      * @return bool success value
668      */
669         public function createTableParams($tablename, $fieldDefs, $indices, $engine = null)
670         {
671                 if (!empty($fieldDefs)) {
672                         $sql = $this->createTableSQLParams($tablename, $fieldDefs, $indices, $engine);
673                         $res = true;
674                         if ($sql) {
675                                 $msg = "Error creating table: $tablename";
676                                 $res = ($res and $this->query($sql,true,$msg));
677                         }
678                         if(!$this->supports("inline_keys")) {
679                                 // handle constraints and indices
680                                 $indicesArr = $this->getConstraintSql($indices, $tablename);
681                                 if (count($indicesArr) > 0)
682                                         foreach ($indicesArr as $indexSql)
683                                                 $res = ($res and $this->query($indexSql, true, "Error creating indexes"));
684                         }
685                         return $res;
686                 }
687                 return false;
688         }
689
690         /**
691          * Implements repair of a db table for a bean.
692          *
693          * @param  SugarBean $bean    SugarBean instance
694          * @param  bool   $execute true if we want the action to take place, false if we just want the sql returned
695          * @return string SQL statement or empty string, depending upon $execute
696          */
697         public function repairTable(SugarBean $bean, $execute = true)
698         {
699                 $indices   = $bean->getIndices();
700                 $fielddefs = $bean->getFieldDefinitions();
701                 $tablename = $bean->getTableName();
702
703                 //Clean the indexes to prevent duplicate definitions
704                 $new_index = array();
705                 foreach($indices as $ind_def){
706                         $new_index[$ind_def['name']] = $ind_def;
707                 }
708                 //jc: added this for beans that do not actually have a table, namely
709                 //ForecastOpportunities
710                 if($tablename == 'does_not_exist' || $tablename == '')
711                         return '';
712
713                 global $dictionary;
714                 $engine=null;
715                 if (isset($dictionary[$bean->getObjectName()]['engine']) && !empty($dictionary[$bean->getObjectName()]['engine']) )
716                         $engine = $dictionary[$bean->getObjectName()]['engine'];
717
718                 return $this->repairTableParams($tablename, $fielddefs,$new_index,$execute,$engine);
719         }
720
721         /**
722          * Can this field be null?
723          * Auto-increment and ID fields can not be null
724          * @param array $vardef
725      * @return bool
726      */
727         protected function isNullable($vardef)
728         {
729
730                 if(isset($vardef['isnull']) && (strtolower($vardef['isnull']) == 'false' || $vardef['isnull'] === false)
731                         && !empty($vardef['required'])) {
732                                 /* required + is_null=false => not null */
733                         return false;
734                 }
735                 if(empty($vardef['auto_increment']) && (empty($vardef['type']) || $vardef['type'] != 'id')
736                                         && (empty($vardef['dbType']) || $vardef['dbType'] != 'id')
737                                         && (empty($vardef['name']) || ($vardef['name'] != 'id' && $vardef['name'] != 'deleted'))
738                 ) {
739                         return true;
740                 }
741                 return false;
742         }
743
744
745         /**
746          * Builds the SQL commands that repair a table structure
747          *
748          * @param  string $tablename
749          * @param  array  $fielddefs Field definitions, in vardef format
750          * @param  array  $indices   Index definitions, in vardef format
751          * @param  bool   $execute   optional, true if we want the queries executed instead of returned
752          * @param  string $engine    optional, MySQL engine
753      * @todo: refactor engine param to be more generic
754      * @return string
755      */
756         public function repairTableParams($tablename, $fielddefs,  $indices, $execute = true, $engine = null)
757         {
758                 //jc: had a bug when running the repair if the tablename is blank the repair will
759                 //fail when it tries to create a repair table
760                 if ($tablename == '' || empty($fielddefs))
761                         return '';
762
763                 //if the table does not exist create it and we are done
764                 $sql = "/* Table : $tablename */\n";
765                 if (!$this->tableExists($tablename)) {
766                         $createtablesql = $this->createTableSQLParams($tablename,$fielddefs,$indices,$engine);
767                         if($execute && $createtablesql){
768                                 $this->createTableParams($tablename,$fielddefs,$indices,$engine);
769                         }
770
771                         $sql .= "/* MISSING TABLE: {$tablename} */\n";
772                         $sql .= $createtablesql . "\n";
773                         return $sql;
774                 }
775
776                 $compareFieldDefs = $this->get_columns($tablename);
777                 $compareIndices = $this->get_indices($tablename);
778
779                 $take_action = false;
780
781                 // do column comparisons
782                 $sql .= "/*COLUMNS*/\n";
783                 foreach ($fielddefs as $name => $value) {
784                         if (isset($value['source']) && $value['source'] != 'db')
785                                 continue;
786
787             // Bug #42406. Skipping breaked vardef without type or name
788             if (isset($value['name']) == false || $value['name'] == false)
789             {
790                 $sql .= "/* NAME IS MISSING IN VARDEF $tablename::$name */\n";
791                 continue;
792             }
793             else if (isset($value['type']) == false || $value['type'] == false)
794             {
795                 $sql .= "/* TYPE IS MISSING IN VARDEF $tablename::$name */\n";
796                 continue;
797             }
798
799                         $name = strtolower($value['name']);
800                         // add or fix the field defs per what the DB is expected to give us back
801                         $this->massageFieldDef($value,$tablename);
802
803                         $ignorerequired=false;
804
805                         //Do not track requiredness in the DB, auto_increment, ID,
806                         // and deleted fields are always required in the DB, so don't force those
807                         if ($this->isNullable($value)) {
808                                 $value['required'] = false;
809                         }
810                         //Should match the conditions in DBManager::oneColumnSQLRep for DB required fields, type='id' fields will sometimes
811
812                         //come into this function as 'type' = 'char', 'dbType' = 'id' without required set in $value. Assume they are correct and leave them alone.
813                         else if (($name == 'id' || $value['type'] == 'id' || (isset($value['dbType']) && $value['dbType'] == 'id'))
814                                 && (!isset($value['required']) && isset($compareFieldDefs[$name]['required'])))
815                         {
816                                 $value['required'] = $compareFieldDefs[$name]['required'];
817                         }
818
819                         if ( !isset($compareFieldDefs[$name]) ) {
820                                 // ok we need this field lets create it
821                                 $sql .= "/*MISSING IN DATABASE - $name -  ROW*/\n";
822                                 $sql .= $this->addColumnSQL($tablename, $value) .  "\n";
823                                 if ($execute)
824                                         $this->addColumn($tablename, $value);
825                                 $take_action = true;
826                         } elseif ( !$this->compareVarDefs($compareFieldDefs[$name],$value)) {
827                                 //fields are different lets alter it
828                                 $sql .= "/*MISMATCH WITH DATABASE - $name -  ROW ";
829                                 foreach($compareFieldDefs[$name] as $rKey => $rValue) {
830                                         $sql .= "[$rKey] => '$rValue'  ";
831                                 }
832                                 $sql .= "*/\n";
833                                 $sql .= "/* VARDEF - $name -  ROW";
834                                 foreach($value as $rKey => $rValue) {
835                                         $sql .= "[$rKey] => '$rValue'  ";
836                                 }
837                                 $sql .= "*/\n";
838
839                                 //jc: oracle will complain if you try to execute a statement that sets a column to (not) null
840                                 //when it is already (not) null
841                                 if ( isset($value['isnull']) && isset($compareFieldDefs[$name]['isnull']) &&
842                                         $value['isnull'] === $compareFieldDefs[$name]['isnull']) {
843                                         unset($value['required']);
844                                         $ignorerequired=true;
845                                 }
846
847                                 //dwheeler: Once a column has been defined as null, we cannot try to force it back to !null
848                                 if ((isset($value['required']) && ($value['required'] === true || $value['required'] == 'true' || $value['required'] === 1))
849                                         && (empty($compareFieldDefs[$name]['required']) || $compareFieldDefs[$name]['required'] != 'true'))
850                                 {
851                                         $ignorerequired = true;
852                                 }
853                                 $altersql = $this->alterColumnSQL($tablename, $value,$ignorerequired);
854                                 if(is_array($altersql)) {
855                                         $altersql = join("\n", $altersql);
856                                 }
857                                 $sql .= $altersql .  "\n";
858                                 if($execute){
859                                         $this->alterColumn($tablename, $value, $ignorerequired);
860                                 }
861                                 $take_action = true;
862                         }
863                 }
864
865                 // do index comparisons
866                 $sql .= "/* INDEXES */\n";
867                 $correctedIndexs = array();
868
869         $compareIndices_case_insensitive = array();
870
871                 // do indices comparisons case-insensitive
872                 foreach($compareIndices as $k => $value){
873                         $value['name'] = strtolower($value['name']);
874                         $compareIndices_case_insensitive[strtolower($k)] = $value;
875                 }
876                 $compareIndices = $compareIndices_case_insensitive;
877                 unset($compareIndices_case_insensitive);
878
879                 foreach ($indices as $value) {
880                         if (isset($value['source']) && $value['source'] != 'db')
881                                 continue;
882
883
884                         $validDBName = $this->getValidDBName($value['name'], true, 'index', true);
885                         if (isset($compareIndices[$validDBName])) {
886                                 $value['name'] = $validDBName;
887                         }
888                     $name = strtolower($value['name']);
889
890                         //Don't attempt to fix the same index twice in one pass;
891                         if (isset($correctedIndexs[$name]))
892                                 continue;
893
894                         //don't bother checking primary nothing we can do about them
895                         if (isset($value['type']) && $value['type'] == 'primary')
896                                 continue;
897
898                         //database helpers do not know how to handle full text indices
899                         if ($value['type']=='fulltext')
900                                 continue;
901
902                         if ( in_array($value['type'],array('alternate_key','foreign')) )
903                                 $value['type'] = 'index';
904
905                         if ( !isset($compareIndices[$name]) ) {
906                                 //First check if an index exists that doesn't match our name, if so, try to rename it
907                                 $found = false;
908                                 foreach ($compareIndices as $ex_name => $ex_value) {
909                                         if($this->compareVarDefs($ex_value, $value, true)) {
910                                                 $found = $ex_name;
911                                                 break;
912                                         }
913                                 }
914                                 if ($found) {
915                                         $sql .=  "/*MISSNAMED INDEX IN DATABASE - $name - $ex_name */\n";
916                                         $rename = $this->renameIndexDefs($ex_value, $value, $tablename);
917                                         if($execute) {
918                                                 $this->query($rename, true, "Cannot rename index");
919                                         }
920                                         $sql .= is_array($rename)?join("\n", $rename). "\n":$rename."\n";
921
922                                 } else {
923                                         // ok we need this field lets create it
924                                         $sql .=  "/*MISSING INDEX IN DATABASE - $name -{$value['type']}  ROW */\n";
925                                         $sql .= $this->addIndexes($tablename,array($value), $execute) .  "\n";
926                                 }
927                                 $take_action = true;
928                                 $correctedIndexs[$name] = true;
929                         } elseif ( !$this->compareVarDefs($compareIndices[$name],$value) ) {
930                                 // fields are different lets alter it
931                                 $sql .= "/*INDEX MISMATCH WITH DATABASE - $name -  ROW ";
932                                 foreach ($compareIndices[$name] as $n1 => $t1) {
933                                         $sql .=  "<$n1>";
934                                         if ( $n1 == 'fields' )
935                                                 foreach($t1 as $rKey => $rValue)
936                                                         $sql .= "[$rKey] => '$rValue'  ";
937                                         else
938                                                 $sql .= " $t1 ";
939                                 }
940                                 $sql .= "*/\n";
941                                 $sql .= "/* VARDEF - $name -  ROW";
942                                 foreach ($value as $n1 => $t1) {
943                                         $sql .= "<$n1>";
944                                         if ( $n1 == 'fields' )
945                                                 foreach ($t1 as $rKey => $rValue)
946                                                         $sql .= "[$rKey] => '$rValue'  ";
947                                         else
948                                                 $sql .= " $t1 ";
949                                 }
950                                 $sql .= "*/\n";
951                                 $sql .= $this->modifyIndexes($tablename,array($value), $execute) .  "\n";
952                                 $take_action = true;
953                                 $correctedIndexs[$name] = true;
954                         }
955                 }
956
957                 return ($take_action === true) ? $sql : '';
958         }
959
960     /**
961      * Compares two vardefs
962      *
963      * @param  array  $fielddef1 This is from the database
964      * @param  array  $fielddef2 This is from the vardef
965      * @param bool $ignoreName Ignore name-only differences?
966      * @return bool   true if they match, false if they don't
967      */
968         public function compareVarDefs($fielddef1, $fielddef2, $ignoreName = false)
969         {
970                 foreach ( $fielddef1 as $key => $value ) {
971                         if ( $key == 'name' && ( strtolower($fielddef1[$key]) == strtolower($fielddef2[$key]) || $ignoreName) )
972                                 continue;
973                         if ( isset($fielddef2[$key]) && $fielddef1[$key] == $fielddef2[$key] )
974                                 continue;
975                         //Ignore len if its not set in the vardef
976                         if ($key == 'len' && empty($fielddef2[$key]))
977                                 continue;
978                         return false;
979                 }
980
981                 return true;
982         }
983
984         /**
985          * Compare a field in two tables
986          * @deprecated
987          * @param  string $name   field name
988          * @param  string $table1
989          * @param  string $table2
990          * @return array  array with keys 'msg','table1','table2'
991          */
992         public function compareFieldInTables($name, $table1, $table2)
993         {
994                 $row1 = $this->describeField($name, $table1);
995                 $row2 = $this->describeField($name, $table2);
996                 $returnArray = array(
997                         'table1' => $row1,
998                         'table2' => $row2,
999                         'msg'    => 'error',
1000                         );
1001
1002                 $ignore_filter = array('Key'=>1);
1003                 if ($row1) {
1004                         if (!$row2) {
1005                                 // Exists on table1 but not table2
1006                                 $returnArray['msg'] = 'not_exists_table2';
1007                         }
1008                         else {
1009                                 if (sizeof($row1) != sizeof($row2)) {
1010                                         $returnArray['msg'] = 'no_match';
1011                                 }
1012                                 else {
1013                                         $returnArray['msg'] = 'match';
1014                                         foreach($row1 as $key => $value){
1015                                                 //ignore keys when checking we will check them when we do the index check
1016                                                 if( !isset($ignore_filter[$key]) && (!isset($row2[$key]) || $row1[$key] !== $row2[$key])){
1017                                                         $returnArray['msg'] = 'no_match';
1018                                                 }
1019                                         }
1020                                 }
1021                         }
1022                 }
1023                 else {
1024                         $returnArray['msg'] = 'not_exists_table1';
1025                 }
1026
1027                 return $returnArray;
1028         }
1029 //
1030 //    /**
1031 //     * Compare an index in two different tables
1032 //     * @deprecated
1033 //     * @param  string $name   index name
1034 //     * @param  string $table1
1035 //     * @param  string $table2
1036 //     * @return array  array with keys 'msg','table1','table2'
1037 //     */
1038 //    public function compareIndexInTables($name, $table1, $table2)
1039 //    {
1040 //        $row1 = $this->describeIndex($name, $table1);
1041 //        $row2 = $this->describeIndex($name, $table2);
1042 //        $returnArray = array(
1043 //            'table1' => $row1,
1044 //            'table2' => $row2,
1045 //            'msg'    => 'error',
1046 //            );
1047 //        $ignore_filter = array('Table'=>1, 'Seq_in_index'=>1,'Cardinality'=>1, 'Sub_part'=>1, 'Packed'=>1, 'Comment'=>1);
1048 //
1049 //        if ($row1) {
1050 //            if (!$row2) {
1051 //                //Exists on table1 but not table2
1052 //                $returnArray['msg'] = 'not_exists_table2';
1053 //            }
1054 //            else {
1055 //                if (sizeof($row1) != sizeof($row2)) {
1056 //                    $returnArray['msg'] = 'no_match';
1057 //                }
1058 //                else {
1059 //                    $returnArray['msg'] = 'match';
1060 //                    foreach ($row1 as $fname => $fvalue) {
1061 //                        if (!isset($row2[$fname])) {
1062 //                            $returnArray['msg'] = 'no_match';
1063 //                        }
1064 //                        if(!isset($ignore_filter[$fname]) && $row1[$fname] != $row2[$fname]){
1065 //                            $returnArray['msg'] = 'no_match';
1066 //                        }
1067 //                    }
1068 //                }
1069 //            }
1070 //        } else {
1071 //            $returnArray['msg'] = 'not_exists_table1';
1072 //        }
1073 //
1074 //        return $returnArray;
1075 //    }
1076
1077
1078         /**
1079          * Creates an index identified by name on the given fields.
1080          *
1081          * @param SugarBean $bean      SugarBean instance
1082          * @param array  $fieldDefs Field definitions, in vardef format
1083          * @param string $name      index name
1084          * @param bool   $unique    optional, true if we want to create an unique index
1085      * @return bool query result
1086      */
1087         public function createIndex(SugarBean $bean, $fieldDefs, $name, $unique = true)
1088         {
1089                 $sql = $this->createIndexSQL($bean, $fieldDefs, $name, $unique);
1090                 $tablename = $bean->getTableName();
1091                 $msg = "Error creating index $name on table: $tablename:";
1092                 return $this->query($sql,true,$msg);
1093         }
1094
1095         /**
1096          * returns a SQL query that creates the indices as defined in metadata
1097          * @param  array  $indices Assoc array with index definitions from vardefs
1098          * @param  string $table Focus table
1099          * @return array  Array of SQL queries to generate indices
1100          */
1101         public function getConstraintSql($indices, $table)
1102         {
1103                 if (!$this->isFieldArray($indices))
1104                         $indices = array($indices);
1105
1106                 $columns = array();
1107
1108                 foreach ($indices as $index) {
1109                         if(!empty($index['db']) && $index['db'] != $this->dbType)
1110                                 continue;
1111                         if (isset($index['source']) && $index['source'] != 'db')
1112                         continue;
1113
1114                         $sql = $this->add_drop_constraint($table, $index);
1115
1116                         if(!empty($sql)) {
1117                                 $columns[] = $sql;
1118                         }
1119                 }
1120
1121                 return $columns;
1122         }
1123
1124         /**
1125          * Adds a new indexes
1126          *
1127          * @param  string $tablename
1128          * @param  array  $indexes   indexes to add
1129          * @param  bool   $execute   true if we want to execute the returned sql statement
1130          * @return string SQL statement
1131          */
1132         public function addIndexes($tablename, $indexes, $execute = true)
1133         {
1134                 $alters = $this->getConstraintSql($indexes, $tablename);
1135                 if ($execute) {
1136                         foreach($alters as $sql) {
1137                                 $this->query($sql, true, "Error adding index: ");
1138                         }
1139                 }
1140                 if(!empty($alters)) {
1141                         $sql = join(";\n", $alters).";\n";
1142                 } else {
1143                         $sql = '';
1144                 }
1145                 return $sql;
1146         }
1147
1148         /**
1149          * Drops indexes
1150          *
1151          * @param  string $tablename
1152          * @param  array  $indexes   indexes to drop
1153          * @param  bool   $execute   true if we want to execute the returned sql statement
1154          * @return string SQL statement
1155          */
1156         public function dropIndexes($tablename, $indexes, $execute = true)
1157         {
1158                 $sqls = array();
1159                 foreach ($indexes as $index) {
1160                         $name =$index['name'];
1161                         $sqls[$name] = $this->add_drop_constraint($tablename,$index,true);
1162                 }
1163                 if (!empty($sqls) && $execute) {
1164                         foreach($sqls as $name => $sql) {
1165                                 unset(self::$index_descriptions[$tablename][$name]);
1166                                 $this->query($sql);
1167                         }
1168                 }
1169                 if(!empty($sqls)) {
1170                         return join(";\n",$sqls).";";
1171                 } else {
1172                         return '';
1173                 }
1174         }
1175
1176         /**
1177          * Modifies indexes
1178          *
1179          * @param  string $tablename
1180          * @param  array  $indexes   indexes to modify
1181          * @param  bool   $execute   true if we want to execute the returned sql statement
1182          * @return string SQL statement
1183          */
1184         public function modifyIndexes($tablename, $indexes, $execute = true)
1185         {
1186                 return $this->dropIndexes($tablename, $indexes, $execute)."\n".
1187                         $this->addIndexes($tablename, $indexes, $execute);
1188         }
1189
1190         /**
1191          * Adds a column to table identified by field def.
1192          *
1193          * @param string $tablename
1194          * @param array  $fieldDefs
1195      * @return bool query result
1196      */
1197         public function addColumn($tablename, $fieldDefs)
1198         {
1199                 $sql = $this->addColumnSQL($tablename, $fieldDefs);
1200                 if ($this->isFieldArray($fieldDefs)){
1201                         $columns = array();
1202                         foreach ($fieldDefs as $fieldDef)
1203                                 $columns[] = $fieldDef['name'];
1204                         $columns = implode(",", $columns);
1205                 }
1206                 else {
1207                         $columns = $fieldDefs['name'];
1208                 }
1209                 $msg = "Error adding column(s) $columns on table: $tablename:";
1210                 return $this->query($sql,true,$msg);
1211         }
1212
1213         /**
1214          * Alters old column identified by oldFieldDef to new fieldDef.
1215          *
1216          * @param string $tablename
1217          * @param array  $newFieldDef
1218          * @param bool   $ignoreRequired optional, true if we are ignoring this being a required field
1219      * @return bool query result
1220      */
1221         public function alterColumn($tablename, $newFieldDef, $ignoreRequired = false)
1222         {
1223                 $sql = $this->alterColumnSQL($tablename, $newFieldDef,$ignoreRequired);
1224                 if ($this->isFieldArray($newFieldDef)){
1225                         $columns = array();
1226                         foreach ($newFieldDef as $fieldDef) {
1227                                 $columns[] = $fieldDef['name'];
1228                         }
1229                         $columns = implode(",", $columns);
1230                 }
1231                 else {
1232                         $columns = $newFieldDef['name'];
1233                 }
1234
1235                 $msg = "Error altering column(s) $columns on table: $tablename:";
1236                 $res = $this->query($sql,true,$msg);
1237                 if($res) {
1238                         $this->getTableDescription($tablename, true); // reload table description after altering
1239                 }
1240                 return $res;
1241         }
1242
1243         /**
1244          * Drops the table associated with a bean
1245          *
1246          * @param SugarBean $bean SugarBean instance
1247      * @return bool query result
1248          */
1249         public function dropTable(SugarBean $bean)
1250         {
1251                 return $this->dropTableName($bean->getTableName());
1252         }
1253
1254         /**
1255          * Drops the table by name
1256          *
1257          * @param string $name Table name
1258      * @return bool query result
1259          */
1260         public function dropTableName($name)
1261         {
1262                 $sql = $this->dropTableNameSQL($name);
1263                 return $this->query($sql,true,"Error dropping table $name:");
1264         }
1265
1266     /**
1267      * Deletes a column identified by fieldDef.
1268      *
1269      * @param SugarBean $bean   SugarBean containing the field
1270      * @param array  $fieldDefs Vardef definition of the field
1271      * @return bool query result
1272      */
1273         public function deleteColumn(SugarBean $bean, $fieldDefs)
1274         {
1275                 $tablename = $bean->getTableName();
1276                 $sql = $this->dropColumnSQL($tablename, $fieldDefs);
1277                 $msg = "Error deleting column(s) on table: $tablename:";
1278                 return $this->query($sql,true,$msg);
1279         }
1280
1281     /**
1282      * Generate a set of Insert statements based on the bean given
1283      *
1284      * @deprecated
1285      *
1286      * @param  SugarBean $bean         the bean from which table we will generate insert stmts
1287      * @param  string $select_query the query which will give us the set of objects we want to place into our insert statement
1288      * @param  int    $start        the first row to query
1289      * @param  int    $count        the number of rows to query
1290      * @param  string $table        the table to query from
1291      * @param bool $is_related_query
1292      * @return string SQL insert statement
1293      */
1294         public function generateInsertSQL(SugarBean $bean, $select_query, $start, $count = -1, $table, $is_related_query = false)
1295         {
1296                 $this->log->info('call to DBManager::generateInsertSQL() is deprecated');
1297                 global $sugar_config;
1298
1299                 $rows_found = 0;
1300                 $count_query = $bean->create_list_count_query($select_query);
1301                 if(!empty($count_query))
1302                 {
1303                         // We have a count query.  Run it and get the results.
1304                         $result = $this->query($count_query, true, "Error running count query for $this->object_name List: ");
1305                         $assoc = $this->fetchByAssoc($result);
1306                         if(!empty($assoc['c']))
1307                         {
1308                                 $rows_found = $assoc['c'];
1309                         }
1310                 }
1311                 if($count == -1){
1312                         $count  = $sugar_config['list_max_entries_per_page'];
1313                 }
1314                 $next_offset = $start + $count;
1315
1316                 $result = $this->limitQuery($select_query, $start, $count);
1317                 // get basic insert
1318                 $sql = "INSERT INTO ".$table;
1319                 $custom_sql = "INSERT INTO ".$table."_cstm";
1320
1321                 // get field definitions
1322                 $fields = $bean->getFieldDefinitions();
1323                 $custom_fields = array();
1324
1325                 if($bean->hasCustomFields()){
1326                         foreach ($fields as $fieldDef){
1327                                 if($fieldDef['source'] == 'custom_fields'){
1328                                         $custom_fields[$fieldDef['name']] = $fieldDef['name'];
1329                                 }
1330                         }
1331                         if(!empty($custom_fields)){
1332                                 $custom_fields['id_c'] = 'id_c';
1333                                 $id_field = array('name' => 'id_c', 'custom_type' => 'id',);
1334                                 $fields[] = $id_field;
1335                         }
1336                 }
1337
1338                 // get column names and values
1339                 $row_array = array();
1340                 $columns = array();
1341                 $cstm_row_array = array();
1342                 $cstm_columns = array();
1343                 $built_columns = false;
1344                 while(($row = $this->fetchByAssoc($result)) != null)
1345                 {
1346                         $values = array();
1347                         $cstm_values = array();
1348                         if(!$is_related_query){
1349                                 foreach ($fields as $fieldDef)
1350                                 {
1351                                         if(isset($fieldDef['source']) && $fieldDef['source'] != 'db' && $fieldDef['source'] != 'custom_fields') continue;
1352                                         $val = $row[$fieldDef['name']];
1353
1354                                         //handle auto increment values here only need to do this on insert not create
1355                                         if ($fieldDef['name'] == 'deleted'){
1356                                                         $values['deleted'] = $val;
1357                                                         if(!$built_columns){
1358                                                         $columns[] = 'deleted';
1359                                                 }
1360                                         }
1361                                         else
1362                                         {
1363                                                 $type = $fieldDef['type'];
1364                                                 if(!empty($fieldDef['custom_type'])){
1365                                                         $type = $fieldDef['custom_type'];
1366                                                 }
1367                                                 // need to do some thing about types of values
1368                                                 if($this->dbType == 'mysql' && $val == '' && ($type == 'datetime' ||  $type == 'date' || $type == 'int' || $type == 'currency' || $type == 'decimal')){
1369                                                         if(!empty($custom_fields[$fieldDef['name']]))
1370                                                                 $cstm_values[$fieldDef['name']] = 'null';
1371                                                         else
1372                                                                 $values[$fieldDef['name']] = 'null';
1373                                                 }else{
1374                                                         if(isset($type) && $type=='int') {
1375                                                                 if(!empty($custom_fields[$fieldDef['name']]))
1376                                                                         $cstm_values[$fieldDef['name']] = $GLOBALS['db']->quote(from_html($val));
1377                                                                 else
1378                                                                         $values[$fieldDef['name']] = $GLOBALS['db']->quote(from_html($val));
1379                                                         } else {
1380                                                                 if(!empty($custom_fields[$fieldDef['name']]))
1381                                                                         $cstm_values[$fieldDef['name']] = "'".$GLOBALS['db']->quote(from_html($val))."'";
1382                                                                 else
1383                                                                         $values[$fieldDef['name']] = "'".$GLOBALS['db']->quote(from_html($val))."'";
1384                                                         }
1385                                                 }
1386                                                 if(!$built_columns){
1387                                                         if(!empty($custom_fields[$fieldDef['name']]))
1388                                                                 $cstm_columns[] = $fieldDef['name'];
1389                                                         else
1390                                                                 $columns[] = $fieldDef['name'];
1391                                                 }
1392                                         }
1393
1394                                 }
1395                         } else {
1396                         foreach ($row as $key=>$val)
1397                         {
1398                                         if($key != 'orc_row'){
1399                                                 $values[$key] = "'$val'";
1400                                                 if(!$built_columns){
1401                                                         $columns[] = $key;
1402                                                 }
1403                                         }
1404                         }
1405                         }
1406                         $built_columns = true;
1407                         if(!empty($values)){
1408                                 $row_array[] = $values;
1409                         }
1410                         if(!empty($cstm_values) && !empty($cstm_values['id_c']) && (strlen($cstm_values['id_c']) > 7)){
1411                                 $cstm_row_array[] = $cstm_values;
1412                         }
1413                 }
1414
1415                 //if (sizeof ($values) == 0) return ""; // no columns set
1416
1417                 // get the entire sql
1418                 $sql .= "(".implode(",", $columns).") ";
1419                 $sql .= "VALUES";
1420                 for($i = 0; $i < count($row_array); $i++){
1421                         $sql .= " (".implode(",", $row_array[$i]).")";
1422                         if($i < (count($row_array) - 1)){
1423                                 $sql .= ", ";
1424                         }
1425                 }
1426                 //custom
1427                 // get the entire sql
1428                 $custom_sql .= "(".implode(",", $cstm_columns).") ";
1429                 $custom_sql .= "VALUES";
1430
1431                 for($i = 0; $i < count($cstm_row_array); $i++){
1432                         $custom_sql .= " (".implode(",", $cstm_row_array[$i]).")";
1433                         if($i < (count($cstm_row_array) - 1)){
1434                                 $custom_sql .= ", ";
1435                         }
1436                 }
1437                 return array('data' => $sql, 'cstm_sql' => $custom_sql, /*'result_count' => $row_count, */ 'total_count' => $rows_found, 'next_offset' => $next_offset);
1438         }
1439
1440         /**
1441          * @deprecated
1442          * Disconnects all instances
1443          */
1444         public function disconnectAll()
1445         {
1446                 DBManagerFactory::disconnectAll();
1447         }
1448
1449         /**
1450          * This function sets the query threshold limit
1451          *
1452          * @param int $limit value of query threshold limit
1453          */
1454         public static function setQueryLimit($limit)
1455         {
1456                 //reset the queryCount
1457                 self::$queryCount = 0;
1458                 self::$queryLimit = $limit;
1459         }
1460
1461         /**
1462          * Returns the static queryCount value
1463          *
1464          * @return int value of the queryCount static variable
1465          */
1466         public static function getQueryCount()
1467         {
1468                 return self::$queryCount;
1469         }
1470
1471
1472         /**
1473          * Resets the queryCount value to 0
1474          *
1475          */
1476         public static function resetQueryCount()
1477         {
1478                 self::$queryCount = 0;
1479         }
1480
1481         /**
1482          * This function increments the global $sql_queries variable
1483          */
1484         public function countQuery()
1485         {
1486                 if (self::$queryLimit != 0 && ++self::$queryCount > self::$queryLimit
1487                         &&(empty($GLOBALS['current_user']) || !is_admin($GLOBALS['current_user']))) {
1488             require_once('include/resource/ResourceManager.php');
1489             $resourceManager = ResourceManager::getInstance();
1490             $resourceManager->notifyObservers('ERR_QUERY_LIMIT');
1491                 }
1492         }
1493
1494         /**
1495          * Pre-process string for quoting
1496          * @internal
1497          * @param string $string
1498      * @return string
1499      */
1500         protected function quoteInternal($string)
1501         {
1502                 return from_html($string);
1503         }
1504
1505         /**
1506          * Return string properly quoted with ''
1507          * @param string $string
1508          * @return string
1509          */
1510         public function quoted($string)
1511         {
1512                 return "'".$this->quote($string)."'";
1513         }
1514
1515         /**
1516      * Quote value according to type
1517      * Numerics aren't quoted
1518      * Dates are converted and quoted
1519      * Rest is just quoted
1520      * @param string $type
1521      * @param string $value
1522      * @return string Quoted value
1523      */
1524     public function quoteType($type, $value)
1525         {
1526             if($type == 'date') {
1527                 return $this->convert($this->quoted($value), "date");
1528             }
1529             if($type == 'time') {
1530                 return $this->convert($this->quoted($value), "time");
1531             }
1532         if(isset($this->type_class[$type]) &&  $this->type_class[$type] == "date") {
1533             return $this->convert($this->quoted($value), "datetime");
1534         }
1535         if($this->isNumericType($type)) {
1536             return 0+$value; // ensure it's numeric
1537         }
1538
1539         return $this->quoted($value);
1540         }
1541
1542     /**
1543      * Quote the strings of the passed in array
1544      *
1545      * The array must only contain strings
1546      *
1547      * @param array $array
1548      * @return array Quoted strings
1549      */
1550         public function arrayQuote(array &$array)
1551         {
1552                 foreach($array as &$val) {
1553                         $val = $this->quote($val);
1554                 }
1555                 return $array;
1556         }
1557
1558     /**
1559      * Frees out previous results
1560      *
1561      * @param resource|bool $result optional, pass if you want to free a single result instead of all results
1562      */
1563         protected function freeResult($result = false)
1564         {
1565                 if($result) {
1566                         $this->freeDbResult($result);
1567                 }
1568                 if($this->lastResult) {
1569                         $this->freeDbResult($this->lastResult);
1570                         $this->lastResult = null;
1571                 }
1572         }
1573
1574         /**
1575          * @abstract
1576          * Check if query has LIMIT clause
1577          * Relevant for now only for Mysql
1578          * @param string $sql
1579          * @return bool
1580          */
1581         protected function hasLimit($sql)
1582         {
1583             return false;
1584         }
1585
1586         /**
1587          * Runs a query and returns a single row containing single value
1588          *
1589          * @param  string   $sql        SQL Statement to execute
1590          * @param  bool     $dieOnError True if we want to call die if the query returns errors
1591          * @param  string   $msg        Message to log if error occurs
1592          * @return array    single value from the query
1593          */
1594         public function getOne($sql, $dieOnError = false, $msg = '')
1595         {
1596                 $this->log->info("Get One: |$sql|");
1597                 if(!$this->hasLimit($sql)) {
1598                     $queryresult = $this->limitQuery($sql, 0, 1, $dieOnError, $msg);
1599                 } else {
1600                     // support old code that passes LIMIT to sql
1601                     // works only for mysql, so do not rely on this
1602                     $queryresult = $this->query($sql, $dieOnError, $msg);
1603                 }
1604                 $this->checkError($msg.' Get One Failed:' . $sql, $dieOnError);
1605                 if (!$queryresult) return false;
1606                 $row = $this->fetchByAssoc($queryresult);
1607                 if(!empty($row)) {
1608                         return array_shift($row);
1609                 }
1610                 return false;
1611         }
1612
1613         /**
1614          * Runs a query and returns a single row
1615          *
1616          * @param  string   $sql        SQL Statement to execute
1617          * @param  bool     $dieOnError True if we want to call die if the query returns errors
1618          * @param  string   $msg        Message to log if error occurs
1619          * @param  bool     $suppress   Message to log if error occurs
1620          * @return array    single row from the query
1621          */
1622         public function fetchOne($sql, $dieOnError = false, $msg = '', $suppress = false)
1623         {
1624                 $this->log->info("Fetch One: |$sql|");
1625                 $this->checkConnection();
1626                 $queryresult = $this->query($sql, $dieOnError, $msg);
1627                 $this->checkError($msg.' Fetch One Failed:' . $sql, $dieOnError);
1628
1629                 if (!$queryresult) return false;
1630
1631                 $row = $this->fetchByAssoc($queryresult);
1632                 if ( !$row ) return false;
1633
1634                 $this->freeResult($queryresult);
1635                 return $row;
1636         }
1637
1638     /**
1639      * Returns the number of rows affected by the last query
1640      * @abstract
1641          * See also affected_rows capability, will return 0 unless the DB supports it
1642      * @param resource $result query result resource
1643      * @return int
1644      */
1645         public function getAffectedRowCount($result)
1646         {
1647                 return 0;
1648         }
1649
1650         /**
1651          * Returns the number of rows returned by the result
1652          *
1653          * This function can't be reliably implemented on most DB, do not use it.
1654          * @abstract
1655          * @deprecated
1656          * @param  resource $result
1657          * @return int
1658          */
1659         public function getRowCount($result)
1660         {
1661             return 0;
1662         }
1663
1664         /**
1665      * Get table description
1666      * @param string $tablename
1667      * @param bool $reload true means load from DB, false allows using cache
1668      * @return array Vardef-format table description
1669      *
1670      */
1671         public function getTableDescription($tablename, $reload = false)
1672         {
1673                 if($reload || empty(self::$table_descriptions[$tablename])) {
1674                         self::$table_descriptions[$tablename] = $this->get_columns($tablename);
1675                 }
1676                 return self::$table_descriptions[$tablename];
1677         }
1678
1679         /**
1680          * Returns the field description for a given field in table
1681          *
1682          * @param  string $name
1683          * @param  string $tablename
1684          * @return array
1685          */
1686         protected function describeField($name, $tablename)
1687         {
1688                 $table = $this->getTableDescription($tablename);
1689                 if(!empty($table) && isset($table[$name]))
1690                         return  $table[$name];
1691
1692                 $table = $this->getTableDescription($tablename, true);
1693
1694                 if(isset($table[$name]))
1695                 return $table[$name];
1696
1697                 return array();
1698         }
1699
1700         /**
1701          * Returns the index description for a given index in table
1702          *
1703          * @param  string $name
1704          * @param  string $tablename
1705          * @return array
1706          */
1707         protected function describeIndex($name, $tablename)
1708         {
1709                 if(isset(self::$index_descriptions[$tablename]) && isset(self::$index_descriptions[$tablename]) && isset(self::$index_descriptions[$tablename][$name])){
1710                         return  self::$index_descriptions[$tablename][$name];
1711                 }
1712
1713                 self::$index_descriptions[$tablename] = $this->get_indices($tablename);
1714
1715                 if(isset(self::$index_descriptions[$tablename][$name])){
1716                         return  self::$index_descriptions[$tablename][$name];
1717                 }
1718
1719                 return array();
1720         }
1721
1722     /**
1723      * Truncates a string to a given length
1724      *
1725      * @param string $string
1726      * @param int    $len    length to trim to
1727      * @return string
1728      *
1729      */
1730         public function truncate($string, $len)
1731         {
1732                 if ( is_numeric($len) && $len > 0)
1733                 {
1734                         $string = mb_substr($string,0,(int) $len, "UTF-8");
1735                 }
1736                 return $string;
1737         }
1738
1739     /**
1740      * Returns the database string needed for concatinating multiple database strings together
1741      *
1742      * @param string $table table name of the database fields to concat
1743      * @param array $fields fields in the table to concat together
1744      * @param string $space Separator between strings, default is single space
1745      * @return string
1746      */
1747         public function concat($table, array $fields, $space = ' ')
1748         {
1749                 if(empty($fields)) return '';
1750                 $elems = array();
1751                 $space = $this->quoted($space);
1752                 foreach ( $fields as $field ) {
1753                         if(!empty($elems)) $elems[] = $space;
1754                         $elems[] = $this->convert("$table.$field", 'IFNULL', array("''"));
1755                 }
1756                 $first = array_shift($elems);
1757                 return "LTRIM(RTRIM(".$this->convert($first, 'CONCAT', $elems)."))";
1758         }
1759
1760         /**
1761          * Given a sql stmt attempt to parse it into the sql and the tokens. Then return the index of this prepared statement
1762          * Tokens can come in the following forms:
1763          * ? - a scalar which will be quoted
1764          * ! - a literal which will not be quoted
1765          * & - binary data to read from a file
1766          *
1767          * @param  string       $sql        The sql to parse
1768          * @return int index of the prepared statement to be used with execute
1769          */
1770         public function prepareQuery($sql)
1771         {
1772                 //parse out the tokens
1773                 $tokens = preg_split('/((?<!\\\)[&?!])/', $sql, -1, PREG_SPLIT_DELIM_CAPTURE);
1774
1775                 //maintain a count of the actual tokens for quick reference in execute
1776                 $count = 0;
1777
1778                 $sqlStr = '';
1779                 foreach ($tokens as $key => $val) {
1780                         switch ($val) {
1781                                 case '?' :
1782                                 case '!' :
1783                                 case '&' :
1784                                         $count++;
1785                                         $sqlStr .= '?';
1786                                         break;
1787
1788                                 default :
1789                                         //escape any special characters
1790                                         $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
1791                                         $sqlStr .= $tokens[$key];
1792                                         break;
1793                         } // switch
1794                 } // foreach
1795
1796                 $this->preparedTokens[] = array('tokens' => $tokens, 'tokenCount' => $count, 'sqlString' => $sqlStr);
1797                 end($this->preparedTokens);
1798                 return key($this->preparedTokens);
1799         }
1800
1801         /**
1802          * Takes a prepared stmt index and the data to replace and creates the query and runs it.
1803          *
1804          * @param  int          $stmt       The index of the prepared statement from preparedTokens
1805          * @param  array    $data               The array of data to replace the tokens with.
1806          * @return resource result set or false on error
1807          */
1808         public function executePreparedQuery($stmt, $data = array())
1809         {
1810                 if(!empty($this->preparedTokens[$stmt])){
1811                         if(!is_array($data)){
1812                                 $data = array($data);
1813                         }
1814
1815                         $pTokens = $this->preparedTokens[$stmt];
1816
1817                         //ensure that the number of data elements matches the number of replacement tokens
1818                         //we found in prepare().
1819                         if(count($data) != $pTokens['tokenCount']){
1820                                 //error the data count did not match the token count
1821                                 return false;
1822                         }
1823
1824                         $query = '';
1825                         $dataIndex = 0;
1826                         $tokens = $pTokens['tokens'];
1827                         foreach ($tokens as $val) {
1828                                 switch ($val) {
1829                                         case '?':
1830                                                 $query .= $this->quote($data[$dataIndex++]);
1831                                                 break;
1832                                         case '&':
1833                                                 $filename = $data[$dataIndex++];
1834                                                 $query .= file_get_contents($filename);
1835                                                 break;
1836                                         case '!':
1837                                                 $query .= $data[$dataIndex++];
1838                                                 break;
1839                                         default:
1840                                                 $query .= $val;
1841                                                 break;
1842                                 }//switch
1843                         }//foreach
1844                         return $this->query($query);
1845                 }else{
1846                         return false;
1847                 }
1848         }
1849
1850         /**
1851          * Run both prepare and execute without the client having to run both individually.
1852          *
1853          * @param  string       $sql        The sql to parse
1854          * @param  array    $data               The array of data to replace the tokens with.
1855          * @return resource result set or false on error
1856          */
1857         public function pQuery($sql, $data = array())
1858         {
1859                 $stmt = $this->prepareQuery($sql);
1860                 return $this->executePreparedQuery($stmt, $data);
1861         }
1862
1863 /********************** SQL FUNCTIONS ****************************/
1864     /**
1865      * Generates sql for create table statement for a bean.
1866      *
1867      * NOTE: does not handle out-of-table constraints, use createConstraintSQL for that
1868      * @param SugarBean $bean SugarBean instance
1869      * @return string SQL Create Table statement
1870      */
1871         public function createTableSQL(SugarBean $bean)
1872         {
1873                 $tablename = $bean->getTableName();
1874                 $fieldDefs = $bean->getFieldDefinitions();
1875                 $indices = $bean->getIndices();
1876                 return $this->createTableSQLParams($tablename, $fieldDefs, $indices);
1877         }
1878
1879         /**
1880          * Generates SQL for insert statement.
1881          *
1882          * @param  SugarBean $bean SugarBean instance
1883          * @return string SQL Create Table statement
1884          */
1885         public function insertSQL(SugarBean $bean)
1886         {
1887                 // get column names and values
1888                 $sql = $this->insertParams($bean->getTableName(), $bean->getFieldDefinitions(), get_object_vars($bean),
1889                         isset($bean->field_name_map)?$bean->field_name_map:null, false);
1890                 return $sql;
1891         }
1892
1893         /**
1894          * Generates SQL for update statement.
1895          *
1896          * @param  SugarBean $bean SugarBean instance
1897          * @param  array  $where Optional, where conditions in an array
1898          * @return string SQL Create Table statement
1899          */
1900         public function updateSQL(SugarBean $bean, array $where = array())
1901         {
1902                 $primaryField = $bean->getPrimaryFieldDefinition();
1903                 $columns = array();
1904         $fields = $bean->getFieldDefinitions();
1905                 // get column names and values
1906                 foreach ($fields as $field => $fieldDef) {
1907                         if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')  continue;
1908                         // Do not write out the id field on the update statement.
1909                 // We are not allowed to change ids.
1910                 if ($fieldDef['name'] == $primaryField['name']) continue;
1911
1912                 // If the field is an auto_increment field, then we shouldn't be setting it.  This was added
1913                 // specially for Bugs and Cases which have a number associated with them.
1914                 if (!empty($bean->field_name_map[$field]['auto_increment'])) continue;
1915
1916                 //custom fields handle their save separately
1917                 if(isset($bean->field_name_map) && !empty($bean->field_name_map[$field]['custom_type']))  continue;
1918
1919                 // no need to clear deleted since we only update not deleted records anyway
1920                 if($fieldDef['name'] == 'deleted' && empty($bean->deleted)) continue;
1921
1922                 if(isset($bean->$field)) {
1923                         $val = from_html($bean->$field);
1924                 } else {
1925                         continue;
1926                 }
1927
1928                 if(!empty($fieldDef['type']) && $fieldDef['type'] == 'bool'){
1929                         $val = $bean->getFieldValue($field);
1930                 }
1931
1932                 if(strlen($val) == 0) {
1933                         if(isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
1934                                 $val = $fieldDef['default'];
1935                         } else {
1936                                 $val = null;
1937                         }
1938                 }
1939
1940                 if(!empty($val) && !empty($fieldDef['len']) && strlen($val) > $fieldDef['len']) {
1941                             $val = $this->truncate($val, $fieldDef['len']);
1942                         }
1943
1944                 if(!is_null($val) || !empty($fieldDef['required'])) {
1945                         $columns[] = "{$fieldDef['name']}=".$this->massageValue($val, $fieldDef);
1946                 } elseif($this->isNullable($fieldDef)) {
1947                         $columns[] = "{$fieldDef['name']}=NULL";
1948                 } else {
1949                     $columns[] = "{$fieldDef['name']}=".$this->emptyValue($fieldDef['type']);
1950                 }
1951                 }
1952
1953                 if ( sizeof($columns) == 0 )
1954                         return ""; // no columns set
1955
1956                 // build where clause
1957                 $where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
1958                 if(isset($fields['deleted'])) {
1959                     $where .= " AND deleted=0";
1960                 }
1961
1962                 return "UPDATE ".$bean->getTableName()."
1963                                         SET ".implode(",", $columns)."
1964                                         $where";
1965         }
1966
1967         /**
1968          * This method returns a where array so that it has id entry if
1969          * where is not an array or is empty
1970          *
1971          * @param  SugarBean $bean SugarBean instance
1972          * @param  array  $where Optional, where conditions in an array
1973          * @return array
1974          */
1975         protected function updateWhereArray(SugarBean $bean, array $where = array())
1976         {
1977                 if (count($where) == 0) {
1978                         $fieldDef = $bean->getPrimaryFieldDefinition();
1979                         $primaryColumn = $fieldDef['name'];
1980
1981                         $val = $bean->getFieldValue($fieldDef['name']);
1982                         if ($val != FALSE){
1983                                 $where[$primaryColumn] = $val;
1984                         }
1985                 }
1986
1987                 return $where;
1988         }
1989
1990         /**
1991          * Returns a where clause without the 'where' key word
1992          *
1993          * The clause returned does not have an 'and' at the beginning and the columns
1994          * are joined by 'and'.
1995          *
1996          * @param  string $table table name
1997          * @param  array  $whereArray Optional, where conditions in an array
1998          * @return string
1999          */
2000         protected function getColumnWhereClause($table, array $whereArray = array())
2001         {
2002                 $where = array();
2003                 foreach ($whereArray as $name => $val) {
2004                         $op = "=";
2005                         if (is_array($val)) {
2006                                 $op = "IN";
2007                                 $temp = array();
2008                                 foreach ($val as $tval){
2009                                         $temp[] = $this->quoted($tval);
2010                                 }
2011                                 $val = implode(",", $temp);
2012                                 $val = "($val)";
2013                         } else {
2014                                 $val = $this->quoted($val);
2015                         }
2016
2017                         $where[] = " $table.$name $op $val";
2018                 }
2019
2020                 if (!empty($where))
2021                         return implode(" AND ", $where);
2022
2023                 return '';
2024         }
2025
2026         /**
2027          * This method returns a complete where clause built from the
2028          * where values specified.
2029          *
2030          * @param  SugarBean $bean SugarBean that describes the table
2031          * @param  array  $whereArray Optional, where conditions in an array
2032          * @return string
2033          */
2034         protected function getWhereClause(SugarBean $bean, array $whereArray=array())
2035         {
2036             return " WHERE " . $this->getColumnWhereClause($bean->getTableName(), $whereArray);
2037         }
2038
2039         /**
2040          * Outputs a correct string for the sql statement according to value
2041          *
2042          * @param  mixed $val
2043          * @param  array $fieldDef field definition
2044          * @return mixed
2045          */
2046         public function massageValue($val, $fieldDef)
2047         {
2048                 $type = $this->getFieldType($fieldDef);
2049
2050                 if(isset($this->type_class[$type])) {
2051                         // handle some known types
2052                         switch($this->type_class[$type]) {
2053                                 case 'bool':
2054                                 case 'int':
2055                                         if (!empty($fieldDef['required']) && $val == ''){
2056                                                 if (isset($fieldDef['default'])){
2057                                                         return $fieldDef['default'];
2058                                                 }
2059                                                 return 0;
2060                                         }
2061                                         return intval($val);
2062                 case 'bigint' :
2063                     $val = (float)$val;
2064                                         if (!empty($fieldDef['required']) && $val == false){
2065                                                 if (isset($fieldDef['default'])){
2066                                                         return $fieldDef['default'];
2067                                                 }
2068                                                 return 0;
2069                                         }
2070                     return $val;
2071                                 case 'float':
2072                                         if (!empty($fieldDef['required'])  && $val == ''){
2073                                                 if (isset($fieldDef['default'])){
2074                                                         return $fieldDef['default'];
2075                                                 }
2076                                                 return 0;
2077                                         }
2078                                         return floatval($val);
2079                                 case 'time':
2080                                 case 'date':
2081                                         // empty date can't be '', so convert it to either NULL or empty date value
2082                                         if($val == '') {
2083                                                 if (!empty($fieldDef['required'])) {
2084                                                         if (isset($fieldDef['default'])) {
2085                                                                 return $fieldDef['default'];
2086                                                         }
2087                                                         return $this->emptyValue($type);
2088                                                 }
2089                                                 return "NULL";
2090                                         }
2091                                         break;
2092                         }
2093                 } else {
2094                     if(!empty($val) && !empty($fieldDef['len']) && strlen($val) > $fieldDef['len']) {
2095                             $val = $this->truncate($val, $fieldDef['len']);
2096                         }
2097                 }
2098
2099                 if ( is_null($val) ) {
2100                         if(!empty($fieldDef['required'])) {
2101                                 if (isset($fieldDef['default'])){
2102                                         return $fieldDef['default'];
2103                                 }
2104                                 return $this->emptyValue($type);
2105                         } else {
2106                                 return "NULL";
2107                         }
2108                 }
2109         if($type == "datetimecombo") {
2110             $type = "datetime";
2111         }
2112                 return $this->convert($this->quoted($val), $type);
2113         }
2114
2115         /**
2116          * Massages the field defintions to fill in anything else the DB backend may add
2117          *
2118          * @param  array  $fieldDef
2119          * @param  string $tablename
2120          * @return array
2121          */
2122         public function massageFieldDef(&$fieldDef, $tablename)
2123         {
2124                 if ( !isset($fieldDef['dbType']) ) {
2125                         if ( isset($fieldDef['dbtype']) )
2126                                 $fieldDef['dbType'] = $fieldDef['dbtype'];
2127                         else
2128                                 $fieldDef['dbType'] = $fieldDef['type'];
2129                 }
2130                 $type = $this->getColumnType($fieldDef['dbType'],$fieldDef['name'],$tablename);
2131                 $matches = array();
2132         // len can be a number or a string like 'max', for example, nvarchar(max)
2133         preg_match_all('/(\w+)(?:\(([0-9]+,?[0-9]*|\w+)\)|)/i', $type, $matches);
2134                 if ( isset($matches[1][0]) )
2135                         $fieldDef['type'] = $matches[1][0];
2136                 if ( isset($matches[2][0]) && empty($fieldDef['len']) )
2137                         $fieldDef['len'] = $matches[2][0];
2138                 if ( !empty($fieldDef['precision']) && is_numeric($fieldDef['precision']) && !strstr($fieldDef['len'],',') )
2139                         $fieldDef['len'] .= ",{$fieldDef['precision']}";
2140                 if (!empty($fieldDef['required']) || ($fieldDef['name'] == 'id' && !isset($fieldDef['required'])) ) {
2141                         $fieldDef['required'] = 'true';
2142                 }
2143         }
2144
2145         /**
2146          * Take an SQL statement and produce a list of fields used in that select
2147          * @param string $selectStatement
2148          * @return array
2149          */
2150         public function getSelectFieldsFromQuery($selectStatement)
2151         {
2152                 $selectStatement = trim($selectStatement);
2153                 if (strtoupper(substr($selectStatement, 0, 6)) == "SELECT")
2154                         $selectStatement = trim(substr($selectStatement, 6));
2155
2156                 //Due to sql functions existing in many selects, we can't use php explode
2157                 $fields = array();
2158                 $level = 0;
2159                 $selectField = "";
2160                 $strLen = strlen($selectStatement);
2161                 for($i = 0; $i < $strLen; $i++)
2162                 {
2163                         $char = $selectStatement[$i];
2164
2165                         if ($char == "," && $level == 0)
2166                         {
2167                                 $field = $this->getFieldNameFromSelect(trim($selectField));
2168                                 $fields[$field] = $selectField;
2169                                 $selectField = "";
2170                         }
2171                         else if ($char == "("){
2172                                 $level++;
2173                                 $selectField .= $char;
2174                         }
2175                         else if($char == ")"){
2176                                 $level--;
2177                                 $selectField .= $char;
2178
2179
2180                         }else{
2181                                 $selectField .= $char;
2182                         }
2183
2184                 }
2185                 $fields[$this->getFieldNameFromSelect($selectField)] = $selectField;
2186                 return $fields;
2187         }
2188
2189         /**
2190          * returns the field name used in a select
2191          * @param string $string SELECT query
2192      * @return string
2193      */
2194         protected function getFieldNameFromSelect($string)
2195         {
2196                 if(strncasecmp($string, "DISTINCT ", 9) == 0) {
2197                         $string = substr($string, 9);
2198                 }
2199                 if (stripos($string, " as ") !== false)
2200                         //"as" used for an alias
2201                         return trim(substr($string, strripos($string, " as ") + 4));
2202                 else if (strrpos($string, " ") != 0)
2203                         //Space used as a delimiter for an alias
2204                         return trim(substr($string, strrpos($string, " ")));
2205                 else if (strpos($string, ".") !== false)
2206                         //No alias, but a table.field format was used
2207                         return substr($string, strpos($string, ".") + 1);
2208                 else
2209                         //Give up and assume the whole thing is the field name
2210                         return $string;
2211         }
2212
2213         /**
2214          * Generates SQL for delete statement identified by id.
2215          *
2216          * @param  SugarBean $bean SugarBean instance
2217          * @param  array  $where where conditions in an array
2218          * @return string SQL Update Statement
2219          */
2220         public function deleteSQL(SugarBean $bean, array $where)
2221         {
2222                 $where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
2223                 return "UPDATE ".$bean->getTableName()." SET deleted=1 $where";
2224         }
2225
2226     /**
2227      * Generates SQL for select statement for any bean identified by id.
2228      *
2229      * @param  SugarBean $bean SugarBean instance
2230      * @param  array  $where where conditions in an array
2231      * @return string SQL Select Statement
2232      */
2233         public function retrieveSQL(SugarBean $bean, array $where)
2234         {
2235                 $where = $this->getWhereClause($bean, $this->updateWhereArray($bean, $where));
2236                 return "SELECT * FROM ".$bean->getTableName()." $where AND deleted=0";
2237         }
2238
2239     /**
2240      * This method implements a generic sql for a collection of beans.
2241      *
2242      * Currently, this function does not support outer joins.
2243      *
2244      * @param array $beans Array of values returned by get_class method as the keys and a bean as
2245      *      the value for that key. These beans will be joined in the sql by the key
2246      *      attribute of field defs.
2247      * @param  array $cols Optional, columns to be returned with the keys as names of bean
2248      *      as identified by get_class of bean. Values of this array is the array of fieldDefs
2249      *      to be returned for a bean. If an empty array is passed, all columns are selected.
2250      * @param  array $whereClause Optional, values with the keys as names of bean as identified
2251      *      by get_class of bean. Each value at the first level is an array of values for that
2252      *      bean identified by name of fields. If we want to pass multiple values for a name,
2253      *      pass it as an array. If where is not passed, all the rows will be returned.
2254      *
2255      * @return string SQL Select Statement
2256      */
2257         public function retrieveViewSQL(array $beans, array $cols = array(), array $whereClause = array())
2258         {
2259                 $relations = array(); // stores relations between tables as they are discovered
2260                 $where = $select = array();
2261                 foreach ($beans as $beanID => $bean) {
2262                         $tableName = $bean->getTableName();
2263                         $beanTables[$beanID] = $tableName;
2264
2265                         $table = $beanID;
2266                         $tables[$table] = $tableName;
2267                         $aliases[$tableName][] = $table;
2268
2269                         // build part of select for this table
2270                         if (is_array($cols[$beanID]))
2271                                 foreach ($cols[$beanID] as $def) $select[] = $table.".".$def['name'];
2272
2273                         // build part of where clause
2274                         if (is_array($whereClause[$beanID])){
2275                                 $where[] = $this->getColumnWhereClause($table, $whereClause[$beanID]);
2276                         }
2277                         // initialize so that it can be used properly in form clause generation
2278                         $table_used_in_from[$table] = false;
2279
2280                         $indices = $bean->getIndices();
2281                         foreach ($indices as $index){
2282                                 if ($index['type'] == 'foreign') {
2283                                         $relationship[$table][] = array('foreignTable'=> $index['foreignTable']
2284                                                                                                 ,'foreignColumn'=>$index['foreignField']
2285                                                                                                 ,'localColumn'=> $index['fields']
2286                                                                                                 );
2287                                 }
2288                         }
2289                         $where[] = " $table.deleted = 0";
2290                 }
2291
2292                 // join these clauses
2293                 $select = !empty($select) ? implode(",", $select) : "*";
2294                 $where = implode(" AND ", $where);
2295
2296                 // generate the from clause. Use relations array to generate outer joins
2297                 // all the rest of the tables will be used as a simple from
2298                 // relations table define relations between table1 and table2 through column on table 1
2299                 // table2 is assumed to joining through primary key called id
2300                 $separator = "";
2301                 $from = ''; $table_used_in_from = array();
2302                 foreach ($relations as $table1 => $rightsidearray){
2303                         if ($table_used_in_from[$table1]) continue; // table has been joined
2304
2305                         $from .= $separator." ".$table1;
2306                         $table_used_in_from[$table1] = true;
2307                         foreach ($rightsidearray as $tablearray){
2308                                 $table2 = $tablearray['foreignTable']; // get foreign table
2309                                 $tableAlias = $aliases[$table2]; // get a list of aliases for this table
2310                                 foreach ($tableAlias as $table2) {
2311                                         //choose first alias that does not match
2312                                         // we are doing this because of self joins.
2313                                         // in case of self joins, the same table will have many aliases.
2314                                         if ($table2 != $table1) break;
2315                                 }
2316
2317                                 $col = $tablearray['foreingColumn'];
2318                                 $name = $tablearray['localColumn'];
2319                                 $from .= " LEFT JOIN $table on ($table1.$name = $table2.$col)";
2320                                 $table_used_in_from[$table2] = true;
2321                         }
2322                         $separator = ",";
2323                 }
2324
2325                 return "SELECT $select FROM $from WHERE $where";
2326         }
2327
2328         /**
2329          * Generates SQL for create index statement for a bean.
2330          *
2331          * @param  SugarBean $bean SugarBean instance
2332          * @param  array  $fields fields used in the index
2333          * @param  string $name index name
2334          * @param  bool   $unique Optional, set to true if this is an unique index
2335          * @return string SQL Select Statement
2336          */
2337         public function createIndexSQL(SugarBean $bean, array $fields, $name, $unique = true)
2338         {
2339                 $unique = ($unique) ? "unique" : "";
2340                 $tablename = $bean->getTableName();
2341                 $columns = array();
2342                 // get column names
2343                 foreach ($fields as $fieldDef)
2344                         $columns[] = $fieldDef['name'];
2345
2346                 if (empty($columns))
2347                         return "";
2348
2349                 $columns = implode(",", $columns);
2350
2351                 return "CREATE $unique INDEX $name ON $tablename ($columns)";
2352         }
2353
2354         /**
2355          * Returns the type of the variable in the field
2356          *
2357          * @param  array $fieldDef Vardef-format field def
2358          * @return string
2359          */
2360         public function getFieldType($fieldDef)
2361         {
2362                 // get the type for db type. if that is not set,
2363                 // get it from type. This is done so that
2364                 // we do not have change a lot of existing code
2365                 // and add dbtype where type is being used for some special
2366                 // purposes like referring to foreign table etc.
2367                 if(!empty($fieldDef['dbType']))
2368                         return  $fieldDef['dbType'];
2369                 if(!empty($fieldDef['dbtype']))
2370                         return  $fieldDef['dbtype'];
2371                 if (!empty($fieldDef['type']))
2372                         return  $fieldDef['type'];
2373                 if (!empty($fieldDef['Type']))
2374                         return  $fieldDef['Type'];
2375                 if (!empty($fieldDef['data_type']))
2376                         return  $fieldDef['data_type'];
2377
2378                 return null;
2379         }
2380
2381     /**
2382      * retrieves the different components from the passed column type as it is used in the type mapping and vardefs
2383      * type format: <baseType>[(<len>[,<scale>])]
2384      * @param string $type Column type
2385      * @return array|bool array containing the different components of the passed in type or false in case the type contains illegal characters
2386      */
2387     public function getTypeParts($type) {
2388         if(preg_match("((?'type'\w+)\s*(?'arg'\((?'len'\w+)\s*(,\s*(?'scale'\d+))*\))*)", $type, $matches))
2389         {
2390             $return = array();  // Not returning matches array as such as we don't want to expose the regex make up on the interface
2391             $return['baseType'] = $matches['type'];
2392             if( isset($matches['arg'])) {
2393                 $return['arg'] = $matches['arg'];
2394             }
2395             if( isset($matches['len'])) {
2396                 $return['len'] = $matches['len'];
2397             }
2398             if( isset($matches['scale'])) {
2399                 $return['scale'] = $matches['scale'];
2400             }
2401             return $return;
2402         } else {
2403             return false;
2404         }
2405     }
2406
2407         /**
2408          * Returns the defintion for a single column
2409          *
2410          * @param  array  $fieldDef Vardef-format field def
2411          * @param  bool   $ignoreRequired  Optional, true if we should ignore this being a required field
2412          * @param  string $table           Optional, table name
2413          * @param  bool   $return_as_array Optional, true if we should return the result as an array instead of sql
2414          * @return string or array if $return_as_array is true
2415          */
2416         protected function oneColumnSQLRep($fieldDef, $ignoreRequired = false, $table = '', $return_as_array = false)
2417         {
2418                 $name = $fieldDef['name'];
2419                 $type = $this->getFieldType($fieldDef);
2420         $colType = $this->getColumnType($type);
2421
2422         if($parts = $this->getTypeParts($colType))
2423         {
2424             $colBaseType = $parts['baseType'];
2425             $defLen =  isset($parts['len']) ? $parts['len'] : '255'; // Use the mappings length (precision) as default if it exists
2426         }
2427
2428         if(!empty($fieldDef['len'])) {
2429             if (in_array($colBaseType, array( 'nvarchar', 'nchar', 'varchar', 'varchar2', 'char',
2430                                           'clob', 'blob', 'text'))) {
2431                     $colType = "$colBaseType(${fieldDef['len']})";
2432             } elseif(($colBaseType == 'decimal' || $colBaseType == 'float')){
2433                   if(!empty($fieldDef['precision']) && is_numeric($fieldDef['precision']))
2434                       if(strpos($fieldDef['len'],',') === false){
2435                           $colType = $colBaseType . "(".$fieldDef['len'].",".$fieldDef['precision'].")";
2436                       }else{
2437                           $colType = $colBaseType . "(".$fieldDef['len'].")";
2438                       }
2439                   else
2440                           $colType = $colBaseType . "(".$fieldDef['len'].")";
2441               }
2442         } else {
2443             if (in_array($colBaseType, array( 'nvarchar', 'nchar', 'varchar', 'varchar2', 'char'))) {
2444                 $colType = "$colBaseType($defLen)";
2445             }
2446         }
2447
2448                 if (isset($fieldDef['default']) && strlen($fieldDef['default']) > 0)
2449                         $default = " DEFAULT ".$this->quoted($fieldDef['default']);
2450                 elseif (!isset($default) && $type == 'bool')
2451                         $default = " DEFAULT 0 ";
2452                 elseif (!isset($default))
2453                         $default = '';
2454
2455                 $auto_increment = '';
2456                 if(!empty($fieldDef['auto_increment']) && $fieldDef['auto_increment'])
2457                         $auto_increment = $this->setAutoIncrement($table , $fieldDef['name']);
2458
2459                 $required = 'NULL';  // MySQL defaults to NULL, SQL Server defaults to NOT NULL -- must specify
2460                 //Starting in 6.0, only ID and auto_increment fields will be NOT NULL in the DB.
2461                 if ((empty($fieldDef['isnull']) || strtolower($fieldDef['isnull']) == 'false') &&
2462                         (!empty($auto_increment) || $name == 'id' || ($fieldDef['type'] == 'id' && !empty($fieldDef['required'])))) {
2463                         $required =  "NOT NULL";
2464                 }
2465                 // If the field is marked both required & isnull=>false - alwqys make it not null
2466                 // Use this to ensure primary key fields never defined as null
2467                 if(isset($fieldDef['isnull']) && (strtolower($fieldDef['isnull']) == 'false' || $fieldDef['isnull'] === false)
2468                         && !empty($fieldDef['required'])) {
2469                         $required =  "NOT NULL";
2470                 }
2471                 if ($ignoreRequired)
2472                         $required = "";
2473
2474                 if ( $return_as_array ) {
2475                         return array(
2476                                 'name' => $name,
2477                                 'colType' => $colType,
2478                 'colBaseType' => $colBaseType,  // Adding base type for easier processing in derived classes
2479                                 'default' => $default,
2480                                 'required' => $required,
2481                                 'auto_increment' => $auto_increment,
2482                                 'full' => "$name $colType $default $required $auto_increment",
2483                                 );
2484                 } else {
2485                         return "$name $colType $default $required $auto_increment";
2486                 }
2487         }
2488
2489         /**
2490          * Returns SQL defintions for all columns in a table
2491          *
2492          * @param  array  $fieldDefs  Vardef-format field def
2493          * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
2494          * @param  string $tablename      Optional, table name
2495          * @return string SQL column definitions
2496          */
2497         protected function columnSQLRep($fieldDefs, $ignoreRequired = false, $tablename)
2498         {
2499                 $columns = array();
2500
2501                 if ($this->isFieldArray($fieldDefs)) {
2502                         foreach ($fieldDefs as $fieldDef) {
2503                                 if(!isset($fieldDef['source']) || $fieldDef['source'] == 'db') {
2504                                         $columns[] = $this->oneColumnSQLRep($fieldDef,false, $tablename);
2505                                 }
2506                         }
2507                         $columns = implode(",", $columns);
2508                 }
2509                 else {
2510                         $columns = $this->oneColumnSQLRep($fieldDefs,$ignoreRequired, $tablename);
2511                 }
2512
2513                 return $columns;
2514         }
2515
2516         /**
2517          * Returns the next value for an auto increment
2518          * @abstract
2519          * @param  string $table Table name
2520          * @param  string $field_name Field name
2521          * @return string
2522          */
2523         public function getAutoIncrement($table, $field_name)
2524         {
2525                 return "";
2526         }
2527
2528         /**
2529          * Returns the sql for the next value in a sequence
2530          * @abstract
2531          * @param  string $table  Table name
2532          * @param  string $field_name  Field name
2533          * @return string
2534          */
2535         public function getAutoIncrementSQL($table, $field_name)
2536         {
2537                 return "";
2538         }
2539
2540         /**
2541          * Either creates an auto increment through queries or returns sql for auto increment
2542          * that can be appended to the end of column defination (mysql)
2543          * @abstract
2544          * @param  string $table Table name
2545          * @param  string $field_name Field name
2546          * @return string
2547          */
2548         protected function setAutoIncrement($table, $field_name)
2549         {
2550                 $this->deleteAutoIncrement($table, $field_name);
2551                 return "";
2552         }
2553
2554     /**
2555      * Sets the next auto-increment value of a column to a specific value.
2556      * @abstract
2557      * @param  string $table Table name
2558      * @param  string $field_name Field name
2559      * @param  int $start_value  Starting autoincrement value
2560      * @return string
2561      *
2562      */
2563         public function setAutoIncrementStart($table, $field_name, $start_value)
2564         {
2565                 return "";
2566         }
2567
2568         /**
2569          * Deletes an auto increment
2570          * @abstract
2571          * @param string $table tablename
2572          * @param string $field_name
2573          */
2574         public function deleteAutoIncrement($table, $field_name)
2575         {
2576                 return;
2577         }
2578
2579         /**
2580          * This method generates sql for adding a column to table identified by field def.
2581          *
2582          * @param  string $tablename
2583          * @param  array  $fieldDefs
2584          * @return string SQL statement
2585          */
2586         public function addColumnSQL($tablename, $fieldDefs)
2587         {
2588             return $this->changeColumnSQL($tablename, $fieldDefs, 'add');
2589         }
2590
2591         /**
2592          * This method genrates sql for altering old column identified by oldFieldDef to new fieldDef.
2593          *
2594          * @param  string $tablename
2595          * @param  array  $newFieldDefs
2596          * @param  bool  $ignorerequired Optional, true if we should ignor this being a required field
2597          * @return string|array SQL statement(s)
2598          */
2599         public function alterColumnSQL($tablename, $newFieldDefs, $ignorerequired = false)
2600         {
2601                 return $this->changeColumnSQL($tablename, $newFieldDefs, 'modify', $ignorerequired);
2602         }
2603
2604         /**
2605          * Generates SQL for dropping a table.
2606          *
2607          * @param  SugarBean $bean Sugarbean instance
2608          * @return string SQL statement
2609          */
2610         public function dropTableSQL(SugarBean $bean)
2611         {
2612                 return $this->dropTableNameSQL($bean->getTableName());
2613         }
2614
2615         /**
2616          * Generates SQL for dropping a table.
2617          *
2618          * @param  string $name table name
2619          * @return string SQL statement
2620          */
2621         public function dropTableNameSQL($name)
2622         {
2623                 return "DROP TABLE ".$name;
2624         }
2625
2626         /**
2627          * Generates SQL for truncating a table.
2628          * @param  string $name  table name
2629          * @return string
2630          */
2631         public function truncateTableSQL($name)
2632         {
2633                 return "TRUNCATE $name";
2634         }
2635
2636         /**
2637          * This method generates sql that deletes a column identified by fieldDef.
2638          *
2639          * @param  SugarBean $bean      Sugarbean instance
2640          * @param  array  $fieldDefs
2641          * @return string SQL statement
2642          */
2643         public function deleteColumnSQL(SugarBean $bean, $fieldDefs)
2644         {
2645                 return $this->dropColumnSQL($bean->getTableName(), $fieldDefs);
2646         }
2647
2648         /**
2649          * This method generates sql that drops a column identified by fieldDef.
2650          * Designed to work like the other addColumnSQL() and alterColumnSQL() functions
2651          *
2652          * @param  string $tablename
2653          * @param  array  $fieldDefs
2654          * @return string SQL statement
2655          */
2656         public function dropColumnSQL($tablename, $fieldDefs)
2657         {
2658                 return $this->changeColumnSQL($tablename, $fieldDefs, 'drop');
2659         }
2660
2661     /**
2662      * Return a version of $proposed that can be used as a column name in any of our supported databases
2663      * Practically this means no longer than 25 characters as the smallest identifier length for our supported DBs is 30 chars for Oracle plus we add on at least four characters in some places (for indicies for example)
2664      * @param string|array $name Proposed name for the column
2665      * @param bool|string $ensureUnique Ensure the name is unique
2666      * @param string $type Name type (table, column)
2667      * @param bool $force Force new name
2668      * @return string|array Valid column name trimmed to right length and with invalid characters removed
2669      */
2670         public function getValidDBName($name, $ensureUnique = false, $type = 'column', $force = false)
2671         {
2672                 if(is_array($name)) {
2673                         $result = array();
2674                         foreach($name as $field) {
2675                                 $result[] = $this->getValidDBName($field, $ensureUnique, $type);
2676                         }
2677                         return $result;
2678                 } else {
2679                     if(strchr($name, ".")) {
2680                         // this is a compound name with dots, handle separately
2681                         $parts = explode(".", $name);
2682                         if(count($parts) > 2) {
2683                             // some weird name, cut to table.name
2684                             array_splice($parts, 0, count($parts)-2);
2685                         }
2686                         $parts = $this->getValidDBName($parts, $ensureUnique, $type, $force);
2687                 return join(".", $parts);
2688                     }
2689                         // first strip any invalid characters - all but word chars (which is alphanumeric and _)
2690                         $name = preg_replace( '/[^\w]+/i', '', $name ) ;
2691                         $len = strlen( $name ) ;
2692                         $maxLen = empty($this->maxNameLengths[$type]) ? $this->maxNameLengths[$type]['column'] : $this->maxNameLengths[$type];
2693                         if ($len <= $maxLen && !$force) {
2694                                 return strtolower($name);
2695                         }
2696                         if ($ensureUnique) {
2697                                 $md5str = md5($name);
2698                                 $tail = substr ( $name, -11) ;
2699                                 $temp = substr($md5str , strlen($md5str)-4 );
2700                                 $result = substr( $name, 0, 10) . $temp . $tail ;
2701                         } else {
2702                                 $result = substr( $name, 0, 11) . substr( $name, 11 - $maxLen);
2703                         }
2704
2705                         return strtolower( $result ) ;
2706                 }
2707         }
2708
2709         /**
2710          * Returns the valid type for a column given the type in fieldDef
2711          *
2712          * @param  string $type field type
2713          * @return string valid type for the given field
2714          */
2715         public function getColumnType($type)
2716         {
2717                 return isset($this->type_map[$type])?$this->type_map[$type]:$type;
2718         }
2719
2720         /**
2721          * Checks to see if passed array is truely an array of defitions
2722          *
2723          * Such an array may have type as a key but it will point to an array
2724          * for a true array of definitions an to a col type for a definition only
2725          *
2726          * @param  mixed $defArray
2727          * @return bool
2728          */
2729         public function isFieldArray($defArray)
2730         {
2731                 if ( !is_array($defArray) )
2732                         return false;
2733
2734                 if ( isset($defArray['type']) ){
2735                         // type key exists. May be an array of defs or a simple definition
2736                         return is_array($defArray['type']); // type is not an array => definition else array
2737                 }
2738
2739                 // type does not exist. Must be array of definitions
2740                 return true;
2741         }
2742
2743         /**
2744          * returns true if the type can be mapped to a valid column type
2745          *
2746          * @param  string $type
2747          * @return bool
2748          */
2749         protected function validColumnType($type)
2750         {
2751                 $type = $this->getColumnType($type);
2752                 return !empty($type);
2753         }
2754
2755         /**
2756          * Generate query for audit table
2757          * @param SugarBean $bean SugarBean that was changed
2758          * @param array $changes List of changes, contains 'before' and 'after'
2759      * @return string  Audit table INSERT query
2760      */
2761         protected function auditSQL(SugarBean $bean, $changes)
2762         {
2763                 global $current_user;
2764                 $sql = "INSERT INTO ".$bean->get_audit_table_name();
2765                 //get field defs for the audit table.
2766                 require('metadata/audit_templateMetaData.php');
2767                 $fieldDefs = $dictionary['audit']['fields'];
2768
2769                 $values=array();
2770                 $values['id'] = $this->massageValue(create_guid(), $fieldDefs['id']);
2771                 $values['parent_id']= $this->massageValue($bean->id, $fieldDefs['parent_id']);
2772                 $values['field_name']= $this->massageValue($changes['field_name'], $fieldDefs['field_name']);
2773                 $values['data_type'] = $this->massageValue($changes['data_type'], $fieldDefs['data_type']);
2774                 if ($changes['data_type']=='text') {
2775                         $bean->fetched_row[$changes['field_name']]=$changes['after'];;
2776                         $values['before_value_text'] = $this->massageValue($changes['before'], $fieldDefs['before_value_text']);
2777                         $values['after_value_text'] = $this->massageValue($changes['after'], $fieldDefs['after_value_text']);
2778                 } else {
2779                         $bean->fetched_row[$changes['field_name']]=$changes['after'];;
2780                         $values['before_value_string'] = $this->massageValue($changes['before'], $fieldDefs['before_value_string']);
2781                         $values['after_value_string'] = $this->massageValue($changes['after'], $fieldDefs['after_value_string']);
2782                 }
2783                 $values['date_created'] = $this->massageValue(TimeDate::getInstance()->nowDb(), $fieldDefs['date_created'] );
2784                 $values['created_by'] = $this->massageValue($current_user->id, $fieldDefs['created_by']);
2785
2786                 $sql .= "(".implode(",", array_keys($values)).") ";
2787                 $sql .= "VALUES(".implode(",", $values).")";
2788                 return $sql;
2789         }
2790
2791     /**
2792      * Saves changes to module's audit table
2793      *
2794      * @param SugarBean $bean Sugarbean instance that was changed
2795      * @param array $changes List of changes, contains 'before' and 'after'
2796      * @return bool query result
2797      *
2798      */
2799         public function save_audit_records(SugarBean $bean, $changes)
2800         {
2801                 return $this->query($this->auditSQL($bean, $changes));
2802         }
2803
2804     /**
2805      * Uses the audit enabled fields array to find fields whose value has changed.
2806      * The before and after values are stored in the bean.
2807      * Uses $bean->fetched_row to compare
2808      *
2809      * @param SugarBean $bean Sugarbean instance that was changed
2810      * @return array
2811      */
2812         public function getDataChanges(SugarBean &$bean)
2813         {
2814                 $changed_values=array();
2815                 $audit_fields=$bean->getAuditEnabledFieldDefinitions();
2816
2817                 if (is_array($audit_fields) and count($audit_fields) > 0) {
2818                         foreach ($audit_fields as $field=>$properties) {
2819                                 if (!empty($bean->fetched_row) && array_key_exists($field, $bean->fetched_row)) {
2820                                         $before_value=$bean->fetched_row[$field];
2821                                         $after_value=$bean->$field;
2822                                         if (isset($properties['type'])) {
2823                                                 $field_type=$properties['type'];
2824                                         } else {
2825                                                 if (isset($properties['dbType']))
2826                                                         $field_type=$properties['dbType'];
2827                                                 else if(isset($properties['data_type']))
2828                                                         $field_type=$properties['data_type'];
2829                                                 else
2830                                                         $field_type=$properties['dbtype'];
2831                                         }
2832
2833                                         //Because of bug #25078(sqlserver haven't 'date' type, trim extra "00:00:00" when insert into *_cstm table).
2834                                         // so when we read the audit datetime field from sqlserver, we have to replace the extra "00:00:00" again.
2835                                         if(!empty($field_type) && $field_type == 'date'){
2836                                                 $before_value = $this->fromConvert($before_value , $field_type);
2837                                         }
2838                                         //if the type and values match, do nothing.
2839                                         if (!($this->_emptyValue($before_value,$field_type) && $this->_emptyValue($after_value,$field_type))) {
2840                                                 if (trim($before_value) !== trim($after_value)) {
2841                             // Bug #42475: Don't directly compare numeric values, instead do the subtract and see if the comparison comes out to be "close enough", it is necessary for floating point numbers.
2842                             // Manual merge of fix 95727f2eed44852f1b6bce9a9eccbe065fe6249f from DBHelper
2843                             // This fix also fixes Bug #44624 in a more generic way and therefore eliminates the need for fix 0a55125b281c4bee87eb347709af462715f33d2d in DBHelper
2844                                                         if (!($this->isNumericType($field_type) &&
2845                                   abs(
2846                                       2*((trim($before_value)+0)-(trim($after_value)+0))/((trim($before_value)+0)+(trim($after_value)+0)) // Using relative difference so that it also works for other numerical types besides currencies
2847                                   )<0.0000000001)) {    // Smaller than 10E-10
2848                                                                 if (!($this->isBooleanType($field_type) && ($this->_getBooleanValue($before_value)== $this->_getBooleanValue($after_value)))) {
2849                                                                         $changed_values[$field]=array('field_name'=>$field,
2850                                                                                 'data_type'=>$field_type,
2851                                                                                 'before'=>$before_value,
2852                                                                                 'after'=>$after_value);
2853                                                                 }
2854                                                         }
2855                                                 }
2856                                         }
2857                                 }
2858                         }
2859                 }
2860                 return $changed_values;
2861         }
2862
2863         /**
2864          * Setup FT indexing
2865          * @abstract
2866          */
2867         public function full_text_indexing_setup()
2868         {
2869                 // Most DBs have nothing to setup, so provide default empty function
2870         }
2871
2872         /**
2873          * Quotes a string for storing in the database
2874          * @deprecated
2875          * Return value will be not surrounded by quotes
2876          *
2877          * @param  string $string
2878          * @return string
2879          */
2880         public function escape_quote($string)
2881         {
2882                 return $this->quote($string);
2883         }
2884
2885         /**
2886          * Quotes a string for storing in the database
2887          * @deprecated
2888          * Return value will be not surrounded by quotes
2889          *
2890          * @param  string $string
2891          * @return string
2892          */
2893         public function quoteFormEmail($string)
2894         {
2895                 return $this->quote($string);
2896         }
2897
2898     /**
2899      * Renames an index using fields definition
2900      *
2901      * @param  array  $old_definition
2902      * @param  array  $new_definition
2903      * @param  string $table_name
2904      * @return string SQL statement
2905      */
2906         public function renameIndexDefs($old_definition, $new_definition, $table_name)
2907         {
2908                 return array($this->add_drop_constraint($table_name,$old_definition,true),
2909                                 $this->add_drop_constraint($table_name,$new_definition), false);
2910         }
2911
2912         /**
2913          * Check if type is boolean
2914          * @param string $type
2915      * @return bool
2916      */
2917         public function isBooleanType($type)
2918         {
2919                 return 'bool' == $type;
2920         }
2921
2922         /**
2923          * Get truth value for boolean type
2924          * Allows 'off' to mean false, along with all 'empty' values
2925          * @param mixed $val
2926      * @return bool
2927          */
2928         protected function _getBooleanValue($val)
2929         {
2930                 //need to put the === sign here otherwise true == 'non empty string'
2931                 if (empty($val) or $val==='off')
2932                         return false;
2933
2934                 return true;
2935         }
2936
2937         /**
2938          * Check if type is a number
2939          * @param string $type
2940      * @return bool
2941          */
2942         public function isNumericType($type)
2943         {
2944             if(isset($this->type_class[$type]) && ($this->type_class[$type] == 'int' || $this->type_class[$type] == 'float')) {
2945                 return true;
2946             }
2947                 return false;
2948         }
2949
2950     /**
2951      * Check if the value is empty value for this type
2952      * @param mixed $val Value
2953      * @param string $type Type (one of vardef types)
2954      * @return bool true if the value if empty
2955      */
2956         protected function _emptyValue($val, $type)
2957         {
2958                 if (empty($val))
2959                         return true;
2960
2961                 if($this->emptyValue($type) == $val) {
2962                         return true;
2963                 }
2964                 switch ($type) {
2965                         case 'decimal':
2966                         case 'decimal2':
2967                         case 'int':
2968                         case 'double':
2969                         case 'float':
2970                         case 'uint':
2971                         case 'ulong':
2972                         case 'long':
2973                         case 'short':
2974                                 return ($val == 0);
2975                         case 'date':
2976                                 if ($val == '0000-00-00')
2977                                         return true;
2978                                 if ($val == 'NULL')
2979                                         return true;
2980                                 return false;
2981                 }
2982
2983                 return false;
2984         }
2985
2986         /**
2987      * @abstract
2988          * Does this type represent text (i.e., non-varchar) value?
2989          * @param string $type
2990      * @return bool
2991          */
2992         public function isTextType($type)
2993         {
2994                 return false;
2995         }
2996
2997         /**
2998          * Check if this DB supports certain capability
2999          * See $this->capabilities for the list
3000          * @param string $cap
3001      * @return bool
3002          */
3003         public function supports($cap)
3004         {
3005                 return !empty($this->capabilities[$cap]);
3006         }
3007
3008         /**
3009          * Create ORDER BY clause for ENUM type field
3010          * @param string $order_by Field name
3011          * @param array $values Possible enum value
3012          * @param string $order_dir Order direction, ASC or DESC
3013      * @return string
3014      */
3015         public function orderByEnum($order_by, $values, $order_dir)
3016         {
3017                 $i = 0;
3018                 $order_by_arr = array();
3019                 foreach ($values as $key => $value) {
3020                         if($key == '') {
3021                                 $order_by_arr[] = "WHEN ($order_by='' OR $order_by IS NULL) THEN $i";
3022                         } else {
3023                                 $order_by_arr[] = "WHEN $order_by=".$this->quoted($key)." THEN $i";
3024                         }
3025                         $i++;
3026                 }
3027                 return "CASE ".implode("\n", $order_by_arr)." ELSE $i END $order_dir\n";
3028         }
3029
3030         /**
3031          * Return representation of an empty value depending on type
3032          * The value is fully quoted, converted, etc.
3033          * @param string $type
3034      * @return mixed Empty value
3035      */
3036         public function emptyValue($type)
3037         {
3038                 if(isset($this->type_class[$type]) && ($this->type_class[$type] == 'bool' || $this->type_class[$type] == 'int' || $this->type_class[$type] == 'float')) {
3039                         return 0;
3040                 }
3041
3042                 return "''";
3043         }
3044
3045         /**
3046          * List of available collation settings
3047      * @abstract
3048          * @return string
3049          */
3050         public function getDefaultCollation()
3051         {
3052                 return null;
3053         }
3054
3055         /**
3056          * List of available collation settings
3057      * @abstract
3058          * @return array
3059          */
3060         public function getCollationList()
3061         {
3062                 return null;
3063         }
3064
3065         /**
3066          * Returns the number of columns in a table
3067          *
3068          * @param  string $table_name
3069          * @return int
3070          */
3071         public function number_of_columns($table_name)
3072         {
3073                 $table = $this->getTableDescription($table_name);
3074                 return count($table);
3075         }
3076
3077         /**
3078          * Return limit query based on given query
3079          * @param string $sql
3080          * @param int $start
3081          * @param int $count
3082          * @param bool $dieOnError
3083          * @param string $msg
3084      * @return resource|bool query result
3085      * @see DBManager::limitQuery()
3086          */
3087         public function limitQuerySql($sql, $start, $count, $dieOnError=false, $msg='')
3088         {
3089                 return $this->limitQuery($sql,$start,$count,$dieOnError,$msg,false);
3090         }
3091
3092         /**
3093          * Return current time in format fit for insertion into DB (with quotes)
3094          * @return string
3095          */
3096         public function now()
3097         {
3098                 return $this->convert($this->quoted(TimeDate::getInstance()->nowDb()), "datetime");
3099         }
3100
3101         /**
3102          * Check if connecting user has certain privilege
3103          * @param string $privilege
3104      * @return bool Privilege allowed?
3105      */
3106         public function checkPrivilege($privilege)
3107         {
3108                 switch($privilege) {
3109                         case "CREATE TABLE":
3110                                 $this->query("CREATE TABLE temp (id varchar(36))");
3111                                 break;
3112                         case "DROP TABLE":
3113                                 $sql = $this->dropTableNameSQL("temp");
3114                                 $this->query($sql);
3115                                 break;
3116                         case "INSERT":
3117                                 $this->query("INSERT INTO temp (id) VALUES ('abcdef0123456789abcdef0123456789abcd')");
3118                                 break;
3119                         case "UPDATE":
3120                                 $this->query("UPDATE temp SET id = '100000000000000000000000000000000000' WHERE id = 'abcdef0123456789abcdef0123456789abcd'");
3121                                 break;
3122                         case 'SELECT':
3123                                 return $this->getOne('SELECT id FROM temp WHERE id=\'100000000000000000000000000000000000\'', false);
3124                         case 'DELETE':
3125                                 $this->query("DELETE FROM temp WHERE id = '100000000000000000000000000000000000'");
3126                                 break;
3127                         case "ADD COLUMN":
3128                                 $test = array("test" => array("name" => "test", "type" => "varchar", "len" => 50));
3129                                 $sql =  $this->changeColumnSQL("temp", $test, "add");
3130                                 $this->query($sql);
3131                                 break;
3132                         case "CHANGE COLUMN":
3133                                 $test = array("test" => array("name" => "test", "type" => "varchar", "len" => 100));
3134                                 $sql =  $this->changeColumnSQL("temp", $test, "modify");
3135                                 $this->query($sql);
3136                                 break;
3137                         case "DROP COLUMN":
3138                                 $test = array("test" => array("name" => "test", "type" => "varchar", "len" => 100));
3139                                 $sql =  $this->changeColumnSQL("temp", $test, "drop");
3140                                 $this->query($sql);
3141                                 break;
3142                         default:
3143                                 return false;
3144                 }
3145                 if($this->checkError("Checking privileges")) {
3146                         return false;
3147                 }
3148                 return true;
3149         }
3150
3151         /**
3152          * Check if the query is a select query
3153          * @param string $query
3154      * @return bool  Is query SELECT?
3155      */
3156         protected function isSelect($query)
3157         {
3158                 $query = trim($query);
3159                 $select_check = strpos(strtolower($query), strtolower("SELECT"));
3160                 //Checks to see if there is union select which is valid
3161                 $select_check2 = strpos(strtolower($query), strtolower("(SELECT"));
3162                 if($select_check==0 || $select_check2==0){
3163                         //Returning false means query is ok!
3164                         return true;
3165                 }
3166                 return false;
3167         }
3168
3169         /**
3170          * Parse fulltext search query with mysql syntax:
3171          *  terms quoted by ""
3172          *  + means the term must be included
3173          *  - means the term must be excluded
3174          *  * or % at the end means wildcard
3175          * @param string $query
3176          * @return array of 3 elements - query terms, mandatory terms and excluded terms
3177          */
3178         public function parseFulltextQuery($query)
3179         {
3180                 /* split on space or comma, double quotes with \ for escape */
3181                 if(strpbrk($query, " ,")) {
3182                         // ("([^"]*?)"|[^" ,]+)((, )+)?
3183                         // '/([^" ,]+|".*?[^\\\\]")(,|\s)\s*/'
3184                         if(!preg_match_all('/("([^"]*?)"|[^"\s,]+)((,\s)+)?/', $query, $m)) {
3185                                 return false;
3186                         }
3187                         $qterms = $m[1];
3188                 } else {
3189                         $qterms = array($query);
3190                 }
3191                 $terms = $must_terms = $not_terms = array();
3192                 foreach($qterms as $item) {
3193                         if($item[0] == '"') {
3194                                 $item = trim($item, '"');
3195                         }
3196                         if($item[0] == '+') {
3197                 if (strlen($item) > 1) {
3198                     $must_terms[] = substr($item, 1);
3199                 }
3200                 continue;
3201                         }
3202                         if($item[0] == '-') {
3203                 if (strlen($item) > 1) {
3204                                     $not_terms[] = substr($item, 1);
3205                 }
3206                 continue;
3207                         }
3208                         $terms[] = $item;
3209                 }
3210                 return array($terms, $must_terms, $not_terms);
3211         }
3212
3213     // Methods to check respective queries
3214         protected $standardQueries = array(
3215                 'ALTER TABLE' => 'verifyAlterTable',
3216                 'DROP TABLE' => 'verifyDropTable',
3217                 'CREATE TABLE' => 'verifyCreateTable',
3218                 'INSERT INTO' => 'verifyInsertInto',
3219                 'UPDATE' => 'verifyUpdate',
3220                 'DELETE FROM' => 'verifyDeleteFrom',
3221         );
3222
3223
3224     /**
3225      * Extract table name from a query
3226      * @param string $query SQL query
3227      * @return string
3228      */
3229         protected function extractTableName($query)
3230         {
3231         $query = preg_replace('/[^A-Za-z0-9_\s]/', "", $query);
3232         $query = trim(str_replace(array_keys($this->standardQueries), '', $query));
3233
3234         $firstSpc = strpos($query, " ");
3235         $end = ($firstSpc > 0) ? $firstSpc : strlen($query);
3236         $table = substr($query, 0, $end);
3237
3238         return $table;
3239         }
3240
3241     /**
3242      * Verify SQl statement using per-DB verification function
3243      * provided the function exists
3244      * @param string $query Query to verify
3245      * @param array $skipTables List of blacklisted tables that aren't checked
3246      * @return string
3247      */
3248         public function verifySQLStatement($query, $skipTables)
3249         {
3250                 $query = trim($query);
3251                 foreach($this->standardQueries as $qstart => $check) {
3252                         if(strncasecmp($qstart, $query, strlen($qstart)) == 0) {
3253                                 if(is_callable(array($this, $check))) {
3254                                         $table = $this->extractTableName($query);
3255                                         if(!in_array($table, $skipTables)) {
3256                                                 return call_user_func(array($this, $check), $table, $query);
3257                                         } else {
3258                                                 $this->log->debug("Skipping table $table as blacklisted");
3259                                         }
3260                                 } else {
3261                                         $this->log->debug("No verification for $qstart on {$this->dbType}");
3262                                 }
3263                                 break;
3264                         }
3265                 }
3266                 return "";
3267         }
3268
3269         /**
3270          * Tests an CREATE TABLE query
3271          * @param string $table The table name to get DDL
3272          * @param string $query The query to test.
3273          * @return string Non-empty if error found
3274          */
3275         protected function verifyCreateTable($table, $query)
3276         {
3277                 $this->log->debug('verifying CREATE statement...');
3278
3279                 // rewrite DDL with _temp name
3280                 $this->log->debug('testing query: ['.$query.']');
3281                 $tempname = $table."__uw_temp";
3282                 $tempTableQuery = str_replace("CREATE TABLE {$table}", "CREATE TABLE $tempname", $query);
3283
3284                 if(strpos($tempTableQuery, '__uw_temp') === false) {
3285                         return 'Could not use a temp table to test query!';
3286                 }
3287
3288                 $this->query($tempTableQuery, false, "Preflight Failed for: {$query}");
3289
3290                 $error = $this->lastError(); // empty on no-errors
3291                 if(!empty($error)) {
3292                         return $error;
3293                 }
3294
3295                 // check if table exists
3296                 $this->log->debug('testing for table: '.$table);
3297                 if(!$this->tableExists($tempname)) {
3298                         return "Failed to create temp table!";
3299                 }
3300
3301                 $this->dropTableName($tempname);
3302                 return '';
3303         }
3304
3305         /**
3306          * Execute multiple queries one after another
3307          * @param array $sqls Queries
3308          * @param bool $dieOnError Die on error, passed to query()
3309          * @param string $msg Error message, passed to query()
3310          * @param bool $suppress Supress errors, passed to query()
3311          * @return resource|bool result set or success/failure bool
3312          */
3313         public function queryArray(array $sqls, $dieOnError = false, $msg = '', $suppress = false)
3314         {
3315                 $last = true;
3316                 foreach($sqls as $sql) {
3317                         if(!($last = $this->query($sql, $dieOnError, $msg, $suppress))) {
3318                                 break;
3319                         }
3320                 }
3321                 return $last;
3322         }
3323
3324         /**
3325          * Fetches the next row in the query result into an associative array
3326          *
3327          * @param  resource $result
3328          * @param  bool $encode Need to HTML-encode the result?
3329          * @return array    returns false if there are no more rows available to fetch
3330          */
3331         public function fetchByAssoc($result, $encode = true)
3332         {
3333             if (empty($result)) return false;
3334
3335             if(is_int($encode) && func_num_args() == 3) {
3336                 // old API: $result, $rowNum, $encode
3337                 $GLOBALS['log']->deprecated("Using row number in fetchByAssoc is not portable and no longer supported. Please fix your code.");
3338                 $encode = func_get_arg(2);
3339             }
3340             $row = $this->fetchRow($result);
3341             if (!empty($row) && $encode && $this->encode) {
3342                 return array_map('to_html', $row);
3343             } else {
3344                return $row;
3345             }
3346         }
3347
3348         /**
3349          * Get DB driver name used for install/upgrade scripts
3350          * @return string
3351          */
3352         public function getScriptName()
3353         {
3354                 // Usually the same name as dbType
3355                 return $this->dbType;
3356         }
3357
3358         /**
3359          * Set database options
3360          * Options are usually db-dependant and derive from $config['dbconfigoption']
3361          * @param array $options
3362          * @return DBManager
3363          */
3364         public function setOptions($options)
3365         {
3366             $this->options = $options;
3367             return $this;
3368         }
3369
3370         /**
3371          * Get DB options
3372          * @return array
3373          */
3374         public function getOptions()
3375         {
3376             return $this->options;
3377         }
3378
3379         /**
3380          * Get DB option by name
3381          * @param string $option Option name
3382          * @return mixed Option value or null if doesn't exist
3383          */
3384         public function getOption($option)
3385         {
3386             if(isset($this->options[$option])) {
3387                 return $this->options[$option];
3388             }
3389             return null;
3390         }
3391
3392         /**
3393          * Commits pending changes to the database when the driver is setup to support transactions.
3394          * Note that the default implementation is applicable for transaction-less or auto commit scenarios.
3395          * @abstract
3396          * @return bool true if commit succeeded, false if it failed
3397          */
3398         public function commit()
3399         {
3400                 $this->log->info("DBManager.commit() stub");
3401                 return true;
3402         }
3403
3404         /**
3405          * Rollsback pending changes to the database when the driver is setup to support transactions.
3406          * Note that the default implementation is applicable for transaction-less or auto commit scenarios.
3407          * Since rollbacks cannot be done, this implementation always returns false.
3408          * @abstract
3409          * @return bool true if rollback succeeded, false if it failed
3410          */
3411         public function rollback()
3412         {
3413                 $this->log->info("DBManager.rollback() stub");
3414                 return false;
3415         }
3416
3417         /**
3418          * Check if this DB name is valid
3419          *
3420          * @param string $name
3421          * @return bool
3422          */
3423         public function isDatabaseNameValid($name)
3424         {
3425                 // Generic case - no slashes, no dots
3426                 return preg_match('#[/.\\\\]#', $name)==0;
3427         }
3428
3429         /**
3430          * Check special requirements for DB installation.
3431          * @abstract
3432          * If everything is OK, return true.
3433          * If something's wrong, return array of error code and parameters
3434          * @return mixed
3435          */
3436         public function canInstall()
3437         {
3438                 return true;
3439         }
3440
3441         /**
3442          * @abstract
3443      * Code run on new database before installing
3444          */
3445         public function preInstall()
3446         {
3447         }
3448
3449         /**
3450      * @abstract
3451          * Code run on new database after installing
3452          */
3453         public function postInstall()
3454         {
3455         }
3456
3457         /**
3458          * Disable keys on the table
3459          * @abstract
3460          * @param string $tableName
3461          */
3462         public function disableKeys($tableName)
3463         {
3464         }
3465
3466         /**
3467          * Re-enable keys on the table
3468          * @abstract
3469          * @param string $tableName
3470          */
3471         public function enableKeys($tableName)
3472         {
3473         }
3474
3475         /**
3476          * Quote string in DB-specific manner
3477          * @param string $string
3478          * @return string
3479          */
3480         abstract public function quote($string);
3481
3482         /**
3483          * Use when you need to convert a database string to a different value; this function does it in a
3484          * database-backend aware way
3485          * Supported conversions:
3486          *      today           return current date
3487          *      left            Take substring from the left
3488          *      date_format     Format date as string, supports %Y-%m-%d, %Y-%m, %Y
3489      *      time_format Format time as string
3490      *      date        Convert date string to datetime value
3491      *      time        Convert time string to datetime value
3492          *      datetime        Convert datetime string to datetime value
3493          *      ifnull          If var is null, use default value
3494          *      concat          Concatenate strings
3495          *      quarter         Quarter number of the date
3496          *      length          Length of string
3497          *      month           Month number of the date
3498          *      add_date        Add specified interval to a date
3499      *      add_time    Add time interval to a date
3500      *      text2char   Convert text field to varchar
3501          *
3502          * @param string $string database string to convert
3503          * @param string $type type of conversion to do
3504          * @param array  $additional_parameters optional, additional parameters to pass to the db function
3505          * @return string
3506          */
3507         abstract public function convert($string, $type, array $additional_parameters = array());
3508
3509         /**
3510          * Converts from Database data to app data
3511          *
3512          * Supported types
3513          * - date
3514          * - time
3515          * - datetime
3516      * - datetimecombo
3517      * - timestamp
3518          *
3519          * @param string $string database string to convert
3520          * @param string $type type of conversion to do
3521          * @return string
3522          */
3523         abstract public function fromConvert($string, $type);
3524
3525     /**
3526      * Parses and runs queries
3527      *
3528      * @param  string   $sql        SQL Statement to execute
3529      * @param  bool     $dieOnError True if we want to call die if the query returns errors
3530      * @param  string   $msg        Message to log if error occurs
3531      * @param  bool     $suppress   Flag to suppress all error output unless in debug logging mode.
3532      * @param  bool     $keepResult Keep query result in the object?
3533      * @return resource|bool result set or success/failure bool
3534      */
3535         abstract public function query($sql, $dieOnError = false, $msg = '', $suppress = false, $keepResult = false);
3536
3537     /**
3538      * Runs a limit query: one where we specify where to start getting records and how many to get
3539      *
3540      * @param  string   $sql     SELECT query
3541      * @param  int      $start   Starting row
3542      * @param  int      $count   How many rows
3543      * @param  boolean  $dieOnError  True if we want to call die if the query returns errors
3544      * @param  string   $msg     Message to log if error occurs
3545      * @param  bool     $execute Execute or return SQL?
3546      * @return resource query result
3547      */
3548         abstract function limitQuery($sql, $start, $count, $dieOnError = false, $msg = '', $execute = true);
3549
3550
3551         /**
3552          * Free Database result
3553          * @param resource $dbResult
3554          */
3555         abstract protected function freeDbResult($dbResult);
3556
3557         /**
3558          * Rename column in the DB
3559          * @param string $tablename
3560          * @param string $column
3561          * @param string $newname
3562          */
3563         abstract function renameColumnSQL($tablename, $column, $newname);
3564
3565         /**
3566          * Returns definitions of all indies for passed table.
3567          *
3568          * return will is a multi-dimensional array that
3569          * categorizes the index definition by types, unique, primary and index.
3570          * <code>
3571          * <?php
3572          * array(                                                              O
3573          *       'index1'=> array (
3574          *           'name'   => 'index1',
3575          *           'type'   => 'primary',
3576          *           'fields' => array('field1','field2')
3577          *           )
3578          *       )
3579          * ?>
3580          * </code>
3581          * This format is similar to how indicies are defined in vardef file.
3582          *
3583          * @param  string $tablename
3584          * @return array
3585          */
3586         abstract public function get_indices($tablename);
3587
3588         /**
3589          * Returns definitions of all indies for passed table.
3590          *
3591          * return will is a multi-dimensional array that
3592          * categorizes the index definition by types, unique, primary and index.
3593          * <code>
3594          * <?php
3595          * array(
3596          *       'field1'=> array (
3597          *           'name'   => 'field1',
3598          *           'type'   => 'varchar',
3599          *           'len' => '200'
3600          *           )
3601          *       )
3602          * ?>
3603          * </code>
3604          * This format is similar to how indicies are defined in vardef file.
3605          *
3606          * @param  string $tablename
3607          * @return array
3608          */
3609         abstract public function get_columns($tablename);
3610
3611         /**
3612          * Generates alter constraint statement given a table name and vardef definition.
3613          *
3614          * Supports both adding and droping a constraint.
3615          *
3616          * @param  string $table      tablename
3617          * @param  array  $definition field definition
3618          * @param  bool   $drop       true if we are dropping the constraint, false if we are adding it
3619          * @return string SQL statement
3620          */
3621         abstract public function add_drop_constraint($table, $definition, $drop = false);
3622
3623         /**
3624          * Returns the description of fields based on the result
3625          *
3626          * @param  resource $result
3627          * @param  boolean  $make_lower_case
3628          * @return array field array
3629          */
3630         abstract public function getFieldsArray($result, $make_lower_case = false);
3631
3632         /**
3633          * Returns an array of tables for this database
3634          *
3635          * @return      array|false     an array of with table names, false if no tables found
3636          */
3637         abstract public function getTablesArray();
3638
3639         /**
3640          * Return's the version of the database
3641          *
3642          * @return string
3643          */
3644         abstract public function version();
3645
3646         /**
3647          * Checks if a table with the name $tableName exists
3648          * and returns true if it does or false otherwise
3649          *
3650          * @param  string $tableName
3651          * @return bool
3652          */
3653         abstract public function tableExists($tableName);
3654
3655         /**
3656          * Fetches the next row in the query result into an associative array
3657          *
3658          * @param  resource $result
3659          * @return array    returns false if there are no more rows available to fetch
3660          */
3661         abstract public function fetchRow($result);
3662
3663         /**
3664          * Connects to the database backend
3665          *
3666          * Takes in the database settings and opens a database connection based on those
3667          * will open either a persistent or non-persistent connection.
3668          * If a persistent connection is desired but not available it will defualt to non-persistent
3669          *
3670          * configOptions must include
3671          * db_host_name - server ip
3672          * db_user_name - database user name
3673          * db_password - database password
3674          *
3675          * @param array   $configOptions
3676          * @param boolean $dieOnError
3677          */
3678         abstract public function connect(array $configOptions = null, $dieOnError = false);
3679
3680         /**
3681          * Generates sql for create table statement for a bean.
3682          *
3683          * @param  string $tablename
3684          * @param  array  $fieldDefs
3685          * @param  array  $indices
3686          * @return string SQL Create Table statement
3687          */
3688         abstract public function createTableSQLParams($tablename, $fieldDefs, $indices);
3689
3690         /**
3691          * Generates the SQL for changing columns
3692          *
3693          * @param string $tablename
3694          * @param array  $fieldDefs
3695          * @param string $action
3696          * @param bool   $ignoreRequired Optional, true if we should ignor this being a required field
3697          * @return string|array
3698          */
3699         abstract protected function changeColumnSQL($tablename, $fieldDefs, $action, $ignoreRequired = false);
3700
3701         /**
3702          * Disconnects from the database
3703          *
3704          * Also handles any cleanup needed
3705          */
3706         abstract public function disconnect();
3707
3708         /**
3709          * Get last database error
3710          * This function should return last error as reported by DB driver
3711          * and should return false if no error condition happened
3712          * @return string|false Error message or false if no error happened
3713          */
3714         abstract public function lastDbError();
3715
3716     /**
3717      * Check if this query is valid
3718      * Validates only SELECT queries
3719      * @param string $query
3720      * @return bool
3721      */
3722         abstract public function validateQuery($query);
3723
3724         /**
3725          * Check if this driver can be used
3726          * @return bool
3727          */
3728         abstract public function valid();
3729
3730         /**
3731          * Check if certain database exists
3732          * @param string $dbname
3733          */
3734         abstract public function dbExists($dbname);
3735
3736         /**
3737          * Get tables like expression
3738          * @param string $like Expression describing tables
3739          * @return array
3740          */
3741         abstract public function tablesLike($like);
3742
3743         /**
3744          * Create a database
3745          * @param string $dbname
3746          */
3747         abstract public function createDatabase($dbname);
3748
3749         /**
3750          * Drop a database
3751          * @param string $dbname
3752          */
3753         abstract public function dropDatabase($dbname);
3754
3755         /**
3756          * Get database configuration information (DB-dependent)
3757          * @return array|null
3758          */
3759         abstract public function getDbInfo();
3760
3761         /**
3762          * Check if certain DB user exists
3763          * @param string $username
3764          */
3765         abstract public function userExists($username);
3766
3767         /**
3768          * Create DB user
3769          * @param string $database_name
3770          * @param string $host_name
3771          * @param string $user
3772          * @param string $password
3773          */
3774         abstract public function createDbUser($database_name, $host_name, $user, $password);
3775
3776         /**
3777          * Check if the database supports fulltext indexing
3778          * Note that database driver can be capable of supporting FT (see supports('fulltext))
3779          * but particular instance can still have it disabled
3780          * @return bool
3781          */
3782         abstract public function full_text_indexing_installed();
3783
3784         /**
3785          * Generate fulltext query from set of terms
3786          * @param string $field Field to search against
3787          * @param array $terms Search terms that may be or not be in the result
3788          * @param array $must_terms Search terms that have to be in the result
3789          * @param array $exclude_terms Search terms that have to be not in the result
3790          */
3791         abstract public function getFulltextQuery($field, $terms, $must_terms = array(), $exclude_terms = array());
3792
3793         /**
3794          * Get install configuration for this DB
3795          * @return array
3796          */
3797         abstract public function installConfig();
3798 }