]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/database/DBHelper.php
Release 6.3.0
[Github/sugarcrm.git] / include / database / DBHelper.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40 * Description: This file is an abstract class and handles the Data base functionality for
41 * the application. It is called by the DBManager class to generate various sql statements.
42 *
43 * All the functions in this class will work with any bean which implements the meta interface.
44 * Please refer the DBManager documentation for the details.
45 *
46 * Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
47 * All Rights Reserved.
48 * Contributor(s): ______________________________________..
49 ********************************************************************************/
50
51 abstract class DBHelper
52 {
53     /**
54      * Instance of the related DBManager object
55      *
56      * @var object DBManager instance
57      */
58     public $db;
59
60     /**
61      * Instance of the related SugarBean object
62      *
63      * @var object SugarBean instance
64      */
65     public $bean;
66
67     /**
68      * Maximum length of identifiers
69      */
70     protected $maxNameLengths;
71
72     /**
73          * Generates sql for create table statement for a bean.
74          *
75          * @param  object $bean SugarBean instance
76          * @return string SQL Create Table statement
77          */
78         public function createTableSQL(
79         SugarBean $bean
80         )
81     {
82                 $tablename = $bean->getTableName();
83                 $fieldDefs = $bean->getFieldDefinitions();
84                 $indices = $bean->getIndices();
85                 return $this->createTableSQLParams($tablename, $fieldDefs, $indices);
86
87         }
88
89         /**
90          * Generates sql for create table statement for a bean.
91          *
92          * @param  string $tablename
93          * @param  array  $fieldDefs
94      * @param  array  $indices
95      * @param  string $engine
96      * @return string SQL Create Table statement
97          */
98         public function createTableSQLParams(
99         $tablename,
100         $fieldDefs,
101         $indices,
102         $engine = null
103         )
104     {
105         $columns = $this->columnSQLRep($fieldDefs, false, $tablename);
106         if (empty($columns))
107             return false;
108
109         $keys = $this->keysSQL($indices);
110         if (!empty($keys))
111             $keys = ",$keys";
112
113         // cn: bug 9873 - module tables do not get created in utf8 with assoc collation
114         return "CREATE TABLE $tablename ($columns $keys) CHARACTER SET utf8 COLLATE utf8_general_ci";
115         }
116
117
118         /**
119      * Generates SQL for insert statement.
120      *
121      * @param  object $bean SugarBean instance
122      * @return string SQL Create Table statement
123      */
124     public function insertSQL(
125         SugarBean $bean
126         )
127     {
128                 // get column names and values
129                 $values = array();
130                 foreach ($bean->getFieldDefinitions() as $fieldDef)
131                 {
132             if (isset($fieldDef['source']) && $fieldDef['source'] != 'db')
133                 continue;
134
135             $val = $bean->getFieldValue($fieldDef['name']);
136             // clean the incoming value..
137             $val = from_html($val);
138             if (strlen($val) <= 0) {
139                 if(isset($fieldDef['default']) && (strlen($fieldDef['default']) > 0))
140                     $val = $fieldDef['default'];
141                 else
142                     $val = null;
143             }
144
145             //handle auto increment values here only need to do this on insert not create
146             if (isset($fieldDef['auto_increment']) && $fieldDef['auto_increment']) {
147                 $values[$fieldDef['name']] =
148                     $this->getAutoIncrementSQL($bean->getTableName(), $fieldDef['name']);
149             }
150             elseif (isset($bean->$fieldDef['name'])) {
151                 // need to do some thing about types of values
152                 $values[$fieldDef['name']] = $this->massageValue($val, $fieldDef);
153             }
154             elseif ($fieldDef['name'] == 'deleted'){
155                 $values['deleted'] = $val;
156             }
157                 }
158
159                 if ( sizeof($values) == 0 )
160             return ""; // no columns set
161
162                 // get the entire sql
163                 return "INSERT INTO ".$bean->getTableName()."
164                     (".implode(",", array_keys($values)).")
165                     VALUES (".implode(",", $values).")";
166         }
167
168         /**
169      * Generates SQL for update statement.
170      *
171      * @param  object $bean SugarBean instance
172      * @param  array  $where Optional, where conditions in an array
173      * @return string SQL Create Table statement
174      */
175     public function updateSQL(
176         SugarBean $bean,
177         array $where = array()
178         )
179     {
180         $primaryField = $bean->getPrimaryFieldDefinition();
181         $columns = array();
182
183                 // get column names and values
184                 foreach ($bean->getFieldDefinitions() as $field => $fieldDef) {
185            // Do not write out the id field on the update statement.
186            // We are not allowed to change ids.
187            if ($fieldDef['name'] == $primaryField['name'])
188                continue;
189
190            // If the field is an auto_increment field, then we shouldn't be setting it.  This was added
191            // specially for Bugs and Cases which have a number associated with them.
192            if (isset($bean->field_name_map[$field]['auto_increment']) &&
193                     $bean->field_name_map[$field]['auto_increment'] == true)
194                continue;
195
196            //custom fields handle their save seperatley
197            if(isset($bean->field_name_map) && !empty($bean->field_name_map[$field]['custom_type']))
198                continue;
199
200            if (isset($bean->$fieldDef['name'])
201                     && (!isset($fieldDef['source']) || $fieldDef['source'] == 'db')) {
202                $val = $bean->getFieldValue($fieldDef['name']);
203                // clean the incoming value..
204                $val = from_html($val);
205
206                // need to do some thing about types of values
207                if (strlen($val) <= 0)
208                     $columns[] = "{$fieldDef['name']}=null";
209                        else
210                     $columns[] = "{$fieldDef['name']}=".$this->massageValue($val, $fieldDef);
211            }
212                 }
213
214                 if ( sizeof($columns) == 0 )
215             return ""; // no columns set
216
217         // build where clause
218         $where = $this->updateWhereArray($bean, $where);
219         $where = $this->getWhereClause($bean, $where);
220
221         return "update ".$bean->getTableName()."
222                     set ".implode(",", $columns)."
223                     $where and deleted=0";
224         }
225
226     /**
227      * This method returns a where array so that it has id entry if
228      * where is not an array or is empty
229      *
230      * @param  object $bean SugarBean instance
231      * @param  array  $where Optional, where conditions in an array
232      * @return array
233      */
234     protected function updateWhereArray(
235         SugarBean $bean,
236         array $where = array()
237         )
238     {
239                 if (sizeof($where) == 0) {
240             $fieldDef = $bean->getPrimaryFieldDefinition();
241             $primaryColumn = $fieldDef['name'];
242
243             $val = $bean->getFieldValue($fieldDef['name']);
244             if ($val != FALSE){
245                 $where[$primaryColumn] = $val;
246             }
247         }
248
249         return $where;
250         }
251
252     /**
253      * Returns a where clause without the 'where' key word
254      *
255      * The clause returned does not have an 'and' at the beginning and the columns
256      * are joined by 'and'.
257      *
258      * @param  string $table table name
259      * @param  array  $whereArray Optional, where conditions in an array
260      * @return string
261      */
262     protected function getColumnWhereClause(
263         $table,
264         array $whereArray = array()
265         )
266     {
267         foreach ($whereArray as $name => $val) {
268             $op = "=";
269             if (is_array($val)) {
270                 $op = "IN";
271                 $temp = array();
272                 foreach ($val as $tval){
273                     $temp[] = "'$tval'";
274                 }
275                 $val = implode(",", $temp);
276                 $val = "($val)";
277             }
278             else
279                 $val = "'$val'";
280
281             $where[] = " $table.$name $op $val";
282         }
283
284         if (is_array($where))
285             $where = implode(" and ", $where);
286
287         return $where;
288     }
289
290     /**
291      * This method returns a complete where clause built from the
292      * where values specified.
293      *
294      * @param  string $table table name
295      * @param  array  $whereArray Optional, where conditions in an array
296      * @return string
297      */
298         protected function getWhereClause(
299         SugarBean $bean,
300         array $whereArray
301         )
302         {
303        return " where " . $this->getColumnWhereClause($bean->getTableName(), $whereArray);
304         }
305
306         /**
307          * Designed to take an SQL statement and produce a list of fields used in that select
308          * @param String $selectStatement
309          */
310         public function getSelectFieldsFromQuery($selectStatement)
311         {
312                 $selectStatement = trim($selectStatement);
313                 if (strtoupper(substr($selectStatement, 0, 6)) == "SELECT")
314                         $selectStatement = trim(substr($selectStatement, 6));
315
316                 //Due to sql functions existing in many selects, we can't use php explode
317                 $fields = array();
318                 $level = 0;
319                 $selectField = "";
320                 $strLen = strlen($selectStatement);
321                 for($i = 0; $i < $strLen; $i++)
322                 {
323                         $char = $selectStatement[$i];
324
325                         if ($char == "," && $level == 0)
326                         {
327                                 $field = $this->getFieldNameFromSelect(trim($selectField));
328                                 $fields[$field] = $selectField;
329                                 $selectField = "";
330                         }
331                         else if ($char == "("){
332                                 $level++;
333                                 $selectField .= $char;
334                         }
335                         else if($char == ")"){
336                                 $level--;
337                                 $selectField .= $char;
338
339
340                         }else{
341                                 $selectField .= $char;
342                         }
343
344                 }
345                 $fields[$this->getFieldNameFromSelect($selectField)] = $selectField;
346                 return $fields;
347         }
348
349         /**
350          * returns the field name used in a select
351          * @param String $string
352          */
353         protected function getFieldNameFromSelect($string)
354         {
355             if(strncasecmp($string, "DISTINCT ", 9) == 0) {
356                 $string = substr($string, 9);
357             }
358                 if (stripos($string, " as ") !== false)
359                         //"as" used for an alias
360                         return trim(substr($string, strripos($string, " as ") + 4));
361                 else if (strrpos($string, " ") != 0)
362                         //Space used as a delimeter for an alias
363                         return trim(substr($string, strrpos($string, " ")));
364                 else if (strpos($string, ".") !== false)
365                         //No alias, but a table.field format was used
366                         return substr($string, strpos($string, ".") + 1);
367                 else
368                         //Give up and assume the whole thing is the field name
369                         return $string;
370         }
371
372     /**
373      * Generates SQL for delete statement identified by id.
374      *
375      * @param  object $bean SugarBean instance
376      * @param  array  $where where conditions in an array
377      * @return string SQL Update Statement
378      */
379         public function deleteSQL(
380         SugarBean $bean,
381         array $where
382         )
383     {
384         $where = $this->updateWhereArray($bean, $where);
385         $where = $this->getWhereClause($bean, $where);
386
387         return "update ".$bean->getTableName()." set deleted=1 $where";
388         }
389
390
391
392     /**
393      * Generates SQL for select statement for any bean identified by id.
394      *
395      * @param  object $bean SugarBean instance
396      * @param  array  $where where conditions in an array
397      * @return string SQL Select Statement
398      */
399         public function retrieveSQL(
400         SugarBean $bean,
401         array $where
402         )
403     {
404         $where = $this->updateWhereArray($bean, $where);
405         $where = $this->getWhereClause($bean, $where);
406
407         return "select * from ".$bean->getTableName()." $where and deleted=0";
408     }
409
410     /**
411      * This method implements a generic sql for a collection of beans.
412      *
413      * Currently, this function does not support outer joins.
414      *
415      * @param  array $bean value returned by get_class method as the keys and a bean as
416      *      the value for that key. These beans will be joined in the sql by the key
417      *      attribute of field defs.
418      * @param  array $cols Optional, columns to be returned with the keys as names of bean
419      *      as identified by get_class of bean. Values of this array is the array of fieldDefs
420      *      to be returned for a bean. If an empty array is passed, all columns are selected.
421      * @param  array $whereClause Optional, values with the keys as names of bean as identified
422      *      by get_class of bean. Each value at the first level is an array of values for that
423      *      bean identified by name of fields. If we want to pass multiple values for a name,
424      *      pass it as an array. If where is not passed, all the rows will be returned.
425      * @return string SQL Select Statement
426      */
427     public function retrieveViewSQL(
428         array $beans,
429         array $cols = array(),
430         array $whereClause = array()
431         )
432     {
433         $relations = array(); // stores relations between tables as they are discovered
434
435         foreach ($beans as $beanID => $bean) {
436             $tableName = $bean->getTableName();
437             $beanTables[$beanID] = $tableName;
438
439             $table = "$beanID";
440             $tables[$table] = $tableName;
441             $aliases[$tableName][] = $table;
442
443             // build part of select for this table
444             if (is_array($cols[$beanID]))
445                 foreach ($cols[$beanID] as $def) $select[] = $table.".".$def['name'];
446
447             // build part of where clause
448             if (is_array($whereClause[$beanID])){
449                 $where[] = $this->getColumnWhereClause($table, $whereClause[$beanID]);
450             }
451             // initialize so that it can be used properly in form clause generation
452             $table_used_in_from[$table] = false;
453
454             $indices = $bean->getIndices();
455             foreach ($indices as $index){
456                 if ($index['type'] == 'foreign') {
457                     $relationship[$table][] = array('foreignTable'=> $index['foreignTable']
458                                                    ,'foreignColumn'=>$index['foreignField']
459                                                    ,'localColumn'=> $index['fields']
460                                                    );
461                 }
462             }
463             $where[] = " $table.deleted = 0";
464         }
465
466         // join these clauses
467         $select = (sizeof($select) > 0) ? implode(",", $select) : "*";
468         $where = implode(" and ", $where);
469
470         // generate the from clause. Use relations array to generate outer joins
471         // all the rest of the tables will be used as a simple from
472         // relations table define relations between table1 and table2 through column on table 1
473         // table2 is assumed to joing through primaty key called id
474         $separator = "";
475         foreach ($relations as $table1 => $rightsidearray){
476             if ($table_used_in_from[$table1]) continue; // table has been joined
477
478             $from .= $separator." ".$table1;
479             $table_used_in_from[$table1] = true;
480             foreach ($rightsidearray as $tablearray){
481                 $table2 = $tablearray['foreignTable']; // get foreign table
482                 $tableAlias = $aliases[$table2]; // get a list of aliases fo thtis table
483                 foreach ($tableAlias as $table2) {
484                     //choose first alias that does not match
485                     // we are doing this because of self joins.
486                     // in case of self joins, the same table will bave many aliases.
487                     if ($table2 != $table1) break;
488                 }
489
490                 $col = $tablearray['foreingColumn'];
491                 $name = $tablearray['localColumn'];
492                 $from .= " LEFT JOIN $table on ($table1.$name = $table2.$col)";
493                 $table_used_in_from[$table2] = true;
494             }
495             $separator = ",";
496         }
497
498         return "select $select from $from where $where";
499     }
500
501     /**
502      * Generates SQL for create index statement for a bean.
503      *
504      * @param  object $bean SugarBean instance
505      * @param  array  $fields fields used in the index
506      * @param  string $name index name
507      * @param  bool   $unique Optional, set to true if this is an unique index
508      * @return string SQL Select Statement
509      */
510         public function createIndexSQL(
511         SugarBean $bean,
512         array $fields,
513         $name,
514         $unique = TRUE
515         )
516     {
517                 $unique = ($unique) ? "unique" : "";
518                 $tablename = $bean->getTableName();
519
520                 // get column names
521                 foreach ($fields as $fieldDef)
522             $columns[] = $fieldDef['name'];
523
524         if (sizeof($columns) == 0)
525             return "";
526
527         $columns = implode(",", $columns);
528
529         return "create $unique index $name on $tablename ($columns)";
530         }
531
532     /**
533      * Returns the type of the variable in the field
534      *
535      * @param  array $fieldDef
536      * @return string
537      */
538     public function getFieldType(
539         $fieldDef
540         )
541     {
542         // get the type for db type. if that is not set,
543         // get it from type. This is done so that
544         // we do not have change a lot of existing code
545         // and add dbtype where type is being used for some special
546         // purposes like referring to foreign table etc.
547         if(!empty($fieldDef['dbType']))
548             return  $fieldDef['dbType'];
549         if(!empty($fieldDef['dbtype']))
550             return  $fieldDef['dbtype'];
551         if (!empty($fieldDef['type']))
552             return  $fieldDef['type'];
553         if (!empty($fieldDef['Type']))
554             return  $fieldDef['Type'];
555         if (!empty($fieldDef['data_type']))
556             return  $fieldDef['data_type'];
557
558         return null;
559     }
560
561     protected function getDefault($fieldDef, $type) {
562         if (isset($fieldDef['default']) && strlen($fieldDef['default']) > 0) {
563             $default = " DEFAULT '".$fieldDef['default']."'";
564         }
565         elseif (!isset($default) && $type == 'bool') {
566             $default = " DEFAULT 0 ";
567         }
568         else {
569             $default = '';
570         }
571
572         return $default;
573     }
574
575     /**
576      * Returns the defintion for a single column
577      *
578      * @param  array  $fieldDef
579      * @param  bool   $ignoreRequired  Optional, true if we should ignor this being a required field
580      * @param  string $table           Optional, table name
581      * @param  bool   $return_as_array Optional, true if we should return the result as an array instead of sql
582      * @return string or array if $return_as_array is true
583      */
584         protected function oneColumnSQLRep(
585         $fieldDef,
586         $ignoreRequired = false,
587         $table = '',
588         $return_as_array = false
589         )
590     {
591
592         $name = $fieldDef['name'];
593         $type = $this->getFieldType($fieldDef);
594         $colType = $this->getColumnType($type, $name, $table);
595
596         if (( $colType == 'nvarchar'
597                                 or $colType == 'nchar'
598                                 or $colType == 'varchar'
599                                 or $colType == 'char'
600                                 or $colType == 'varchar2') ) {
601             if( !empty($fieldDef['len']))
602                 $colType .= "(".$fieldDef['len'].")";
603             else
604                 $colType .= "(255)";
605         }
606        if($colType == 'decimal' || $colType == 'float'){
607                 if(!empty($fieldDef     ['len'])){
608                         if(!empty($fieldDef['precision']) && is_numeric($fieldDef['precision']))
609                                 if(strpos($fieldDef     ['len'],',') === false){
610                             $colType .= "(".$fieldDef['len'].",".$fieldDef['precision'].")";
611                                 }else{
612                             $colType .= "(".$fieldDef['len'].")";
613                                 }
614                         else
615                             $colType .= "(".$fieldDef['len'].")";
616                 }
617        }
618
619
620        $default = $this->getDefault($fieldDef, $type);
621
622         $auto_increment = '';
623         if(!empty($fieldDef['auto_increment']) && $fieldDef['auto_increment'])
624                 $auto_increment = $this->setAutoIncrement($table , $fieldDef['name']);
625
626         $required = 'NULL';  // MySQL defaults to NULL, SQL Server defaults to NOT NULL -- must specify
627         //Starting in 6.0, only ID and auto_increment fields will be NOT NULL in the DB.
628         if ((empty($fieldDef['isnull']) || strtolower($fieldDef['isnull']) == 'false') &&
629                     (!empty($auto_increment) || $name == 'id' || ($fieldDef['type'] == 'id' && !empty($fieldDef['required'])))) {
630             $required =  "NOT NULL";
631         }
632         // If the field is marked both required & isnull=>false - alwqys make it not null
633         // Use this to ensure primary key fields never defined as null
634         if(isset($fieldDef['isnull']) && (strtolower($fieldDef['isnull']) == 'false' || $fieldDef['isnull'] === false)
635             && !empty($fieldDef['required'])) {
636             $required =  "NOT NULL";
637         }
638                 if ($ignoreRequired)
639             $required = "";
640
641         if ( $return_as_array )
642             return array(
643                 'name' => $name,
644                 'colType' => $colType,
645                 'default' => $default,
646                 'required' => $required,
647                 'auto_increment' => $auto_increment,
648                 'full' => "$name $colType $default $required $auto_increment",
649                 );
650             else
651                 return "$name $colType $default $required $auto_increment";
652
653         }
654
655     /**
656      * Returns SQL defintions for all columns in a table
657      *
658      * @param  array  $fieldDefs
659      * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
660      * @param  string $tablename      Optional, table name
661      * @return string SQL column definitions
662      */
663         protected function columnSQLRep(
664         $fieldDefs,
665         $ignoreRequired = false,
666         $tablename
667         )
668     {
669                 $columns = array();
670
671                 if ($this->isFieldArray($fieldDefs)) {
672                         foreach ($fieldDefs as $fieldDef) {
673                                 if(!isset($fieldDef['source']) || $fieldDef['source'] == 'db') {
674                                         $columns[] = $this->oneColumnSQLRep($fieldDef,false, $tablename);
675                                 }
676                         }
677                         $columns = implode(",", $columns);
678                 }
679                 else {
680                         $columns = $this->oneColumnSQLRep($fieldDefs,$ignoreRequired, $tablename);
681                 }
682
683                 return $columns;
684         }
685
686         /**
687      * Returns the next value for an auto increment
688      *
689      * @param  string $table tablename
690      * @param  string $field_name
691      * @return string
692      */
693         public function getAutoIncrement(
694         $table,
695         $field_name
696         )
697     {
698                 return "";
699         }
700
701         /**
702      * Returns the sql for the next value in a sequence
703      *
704      * @param  string $table tablename
705      * @param  string $field_name
706      * @return string
707      */
708     public function getAutoIncrementSQL(
709         $table,
710         $field_name
711         )
712     {
713         return "";
714     }
715
716
717
718         /**
719      * Either creates an auto increment through queries or returns sql for auto increment
720      * that can be appended to the end of column defination (mysql)
721      *
722      * @param  string $table tablename
723      * @param  string $field_name
724      * @return string
725      */
726         protected function setAutoIncrement(
727         $table,
728         $field_name
729         )
730     {
731         $this->deleteAutoIncrement($table, $field_name);
732         return "";
733         }
734
735
736     /**
737      * Sets the next auto-increment value of a column to a specific value.
738      *
739      * @param  string $table tablename
740      * @param  string $field_name
741      */
742     public function setAutoIncrementStart(
743         $table,
744         $field_name,
745         $start_value
746         )
747     {
748         return "";
749     }
750
751         /**
752      * Deletes an auto increment (for oracle not mysql)
753      *
754      * @param string $table tablename
755      * @param string $field_name
756      */
757         public function deleteAutoIncrement(
758         $table,
759         $field_name
760         )
761     {
762         return;
763         }
764
765         /**
766      * Generates the SQL for changing columns
767      *
768      * @param string $tablename
769      * @param array  $fieldDefs
770      * @param string $action
771      * @param bool   $ignoreRequired Optional, true if we should ignor this being a required field
772          */
773         abstract protected function changeColumnSQL(
774         $tablename,
775         $fieldDefs,
776         $action,
777         $ignoreRequired = false);
778
779     /**
780      * This method generates sql for adding a column to table identified by field def.
781      *
782      * @param  string $tablename
783      * @param  array  $fieldDefs
784      * @return string SQL statement
785      */
786         public function addColumnSQL(
787         $tablename,
788         $fieldDefs
789         )
790     {
791        return $this->changeColumnSQL($tablename, $fieldDefs, 'add');
792         }
793
794     /**
795      * This method genrates sql for altering old column identified by oldFieldDef to new fieldDef.
796      *
797      * @param  string $tablename
798      * @param  array  $newFieldDefs
799      * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
800      * @return string SQL statement
801      */
802         public function alterColumnSQL(
803         $tablename,
804         $newFieldDefs,
805         $ignorerequired = false
806         )
807     {
808         return $this->changeColumnSQL($tablename, $newFieldDefs, 'modify', $ignorerequired);
809     }
810
811     /**
812      * Generates SQL for dropping a table.
813      *
814      * @param  object $bean Sugarbean instance
815      * @return string SQL statement
816      */
817         public function dropTableSQL(
818         SugarBean $bean
819         )
820     {
821                 return $this->dropTableNameSQL($bean->getTableName());
822         }
823
824         /**
825      * Generates SQL for dropping a table.
826      *
827      * @param  string $name table name
828      * @return string SQL statement
829      */
830         public function dropTableNameSQL(
831         $name
832         )
833     {
834                 return "drop table if exists ".$name;
835         }
836
837     /**
838      * This method generates sql that deletes a column identified by fieldDef.
839      *
840      * @param  object $bean      Sugarbean instance
841      * @param  array  $fieldDefs
842      * @return string SQL statement
843      */
844         public function deleteColumnSQL(
845         SugarBean $bean,
846         $fieldDefs
847         )
848     {
849         if ($this->isFieldArray($fieldDefs))
850             foreach ($fieldDefs as $fieldDef)
851                 $columns[] = $fieldDef['name'];
852         else
853             $columns[] = $fieldDefs['name'];
854
855         return "alter table ".$bean->getTableName()." drop (".implode(", ", $columns).")";
856         }
857
858     /**
859      * This method generates sql that drops a column identified by fieldDef.
860      * Designed to work like the other addColumnSQL() and alterColumnSQL() functions
861      *
862      * @param  string $tablename
863      * @param  array  $fieldDefs
864      * @return string SQL statement
865      */
866         public function dropColumnSQL(
867         $tablename,
868         $fieldDefs
869         )
870     {
871         $sql = $this->changeColumnSQL(
872             $tablename,
873             $fieldDefs,
874             'drop'
875             );
876         return $sql;
877
878         }
879
880     /**
881      * Generates SQL for key statement for any bean identified by id.
882      *
883      * The passes array is an array of field definitions or a field definition
884      * itself. The keys generated will be either primary, foreign, unique, index
885      * or none at all depending on the setting of the "key" parameter of a field definition
886      *
887      * @param  array  $indices
888      * @param  bool   $alter_table
889      * @param  string $alter_action
890      * @return string SQL Statement
891      */
892     protected function keysSQL(
893         $indices,
894         $alter_table = false,
895         $alter_action = ''
896         )
897         {
898         return '';
899     }
900
901     /**
902      * Outputs a correct string for the sql statement according to value
903      *
904      * @param  mixed $val
905      * @param  array $fieldDef field definition
906      * @return mixed
907      */
908         public function massageValue(
909         $val,
910         $fieldDef
911         )
912     {
913         if ( strlen($val) <= 0 )
914             return "''";
915
916         $type = $this->getFieldType($fieldDef);
917
918         switch ($type) {
919         case 'int':
920         case 'double':
921         case 'float':
922         case 'uint':
923         case 'ulong':
924         case 'long':
925         case 'short':
926         case 'tinyint':
927         case 'bool':
928             return $val;
929             break;
930                 }
931
932         $qval = $this->quote($val);
933
934         switch ($type) {
935         case 'varchar':
936         case 'char':
937         case 'longtext':
938         case 'text':
939         case 'enum':
940         case 'multienum':
941         case 'html':
942         case 'blob':
943         case 'longblob':
944         case 'clob':
945         case 'id':
946         case 'datetime':
947             return $qval;
948             break;
949         case 'date':
950         case 'time':
951             return "$qval";
952             break;
953         }
954
955         return $val;
956         }
957
958     /**
959      * Massages the field defintions to fill in anything else the DB backend may add
960      *
961      * @param  array  $fieldDef
962      * @param  string $tablename
963      * @return array
964      */
965     public function massageFieldDef(
966         &$fieldDef,
967         $tablename
968         )
969     {
970         if ( !isset($fieldDef['dbType']) ) {
971             if ( isset($fieldDef['dbtype']) )
972                 $fieldDef['dbType'] = $fieldDef['dbtype'];
973             else
974                 $fieldDef['dbType'] = $fieldDef['type'];
975         }
976         $type = $this->getColumnType($fieldDef['dbType'],$fieldDef['name'],$tablename);
977         $matches = array();
978         preg_match_all("/(\w+)(?:\(([0-9]+,?[0-9]*)\)|)/i", $type, $matches);
979         if ( isset($matches[1][0]) )
980             $fieldDef['type'] = $matches[1][0];
981         if ( isset($matches[2][0]) && empty($fieldDef['len']) )
982             $fieldDef['len'] = $matches[2][0];
983         if ( !empty($fieldDef['precision']) && is_numeric($fieldDef['precision']) && !strstr($fieldDef['len'],',') )
984             $fieldDef['len'] .= ",{$fieldDef['precision']}";
985         if (isset($fieldDef['required']) && ($fieldDef['required'] == true
986                     || $fieldDef['required'] == '1'
987                     || $fieldDef['required'] == 1)
988                 || ($fieldDef['name'] == 'id' && !isset($fieldDef['required'])) )
989             $fieldDef['required'] = 'true';
990     }
991
992     /*
993      * Return a version of $proposed that can be used as a column name in any of our supported databases
994      * 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)
995      * @param string $name Proposed name for the column
996      * @param string $ensureUnique
997      * @return string Valid column name trimmed to right length and with invalid characters removed
998      */
999      public function getValidDBName ($name, $ensureUnique = false, $type = 'column', $force = false)
1000     {
1001         if(is_array($name))
1002         {
1003             $result = array();
1004             foreach($name as $field)
1005             {
1006                 $result[] = $this->getValidDBName($field, $ensureUnique, $type);
1007             }
1008         }else
1009         {
1010             // first strip any invalid characters - all but alphanumerics and -
1011             $name = preg_replace ( '/[^\w-]+/i', '', $name ) ;
1012             $len = strlen ( $name ) ;
1013             $result = $name;
1014             $maxLen = empty($this->maxNameLengths[$type]) ? $this->maxNameLengths[$type]['column'] : $this->maxNameLengths[$type];
1015             if ($len <= $maxLen && !$force)
1016             {
1017                 return strtolower($name);
1018             }
1019             if ($ensureUnique)
1020             {
1021                 $md5str = md5($name);
1022                 $tail = substr ( $name, -11) ;
1023                 $temp = substr($md5str , strlen($md5str)-4 );
1024                 $result = substr ( $name, 0, 10) . $temp . $tail ;
1025             }
1026             else
1027             {
1028                 $result = substr ( $name, 0, 11) . substr ( $name, 11 - $maxLen);
1029             }
1030
1031             return strtolower ( $result ) ;
1032         }
1033     }
1034
1035     /**
1036      * Returns the valid type for a column given the type in fieldDef
1037      *
1038      * @param  string $type field type
1039      * @return string valid type for the given field
1040      */
1041     abstract public function getColumnType(
1042         $type,
1043         $name = '',
1044         $table = ''
1045         );
1046
1047     /**
1048      * Checks to see if passed array is truely an array of defitions
1049      *
1050      * Such an array may have type as a key but it will point to an array
1051      * for a true array of definitions an to a col type for a definition only
1052      *
1053      * @param  mixed $defArray
1054      * @return bool
1055      */
1056     public function isFieldArray(
1057         $defArray
1058         )
1059     {
1060         if ( !is_array($defArray) )
1061             return false;
1062
1063         if ( isset($defArray['type']) ){
1064             // type key exists. May be an array of defs or a simple definition
1065             $type = $defArray['type'];
1066             return is_array($type); // type is not an array => definition else array
1067         }
1068
1069         // type does not exist. Must be array of definitions
1070         return true;
1071     }
1072
1073     /**
1074      * returns true if the type can be mapped to a valid column type
1075      *
1076      * @param  string $type
1077      * @return bool
1078      */
1079     protected function validColumnType(
1080         $type
1081         )
1082     {
1083         $coltype = $this->getColumnType($type);
1084         return ($coltype) ? true : false;
1085     }
1086
1087     /**
1088      * Saves changes to module's audit table
1089      *
1090      * @param object $bean    Sugarbean instance
1091      * @param array  $changes changes
1092      * @see DBHelper::getDataChanges()
1093      */
1094     public function save_audit_records(SugarBean $bean, $changes)
1095         {
1096                 global $current_user;
1097                 $sql = "INSERT INTO ".$bean->get_audit_table_name();
1098                 //get field defs for the audit table.
1099                 require('metadata/audit_templateMetaData.php');
1100                 $fieldDefs = $dictionary['audit']['fields'];
1101
1102                 $values=array();
1103                 $values['id']=$this->massageValue(create_guid(), $fieldDefs['id']);
1104                 $values['parent_id']=$bean->dbManager->getHelper()->massageValue($bean->id, $fieldDefs['parent_id']);
1105                 $values['field_name']=$bean->dbManager->getHelper()->massageValue($changes['field_name'], $fieldDefs['field_name']);
1106                 $values['data_type']=$bean->dbManager->getHelper()->massageValue($changes['data_type'], $fieldDefs['data_type']);
1107                 if ($changes['data_type']=='text') {
1108                         $bean->fetched_row[$changes['field_name']]=$changes['after'];;
1109                         $values['before_value_text']=$bean->dbManager->getHelper()->massageValue($changes['before'], $fieldDefs['before_value_text']);
1110                         $values['after_value_text']=$bean->dbManager->getHelper()->massageValue($changes['after'], $fieldDefs['after_value_text']);
1111                 } else {
1112                         $bean->fetched_row[$changes['field_name']]=$changes['after'];;
1113                         $values['before_value_string']=$bean->dbManager->getHelper()->massageValue($changes['before'], $fieldDefs['before_value_string']);
1114                         $values['after_value_string']=$bean->dbManager->getHelper()->massageValue($changes['after'], $fieldDefs['after_value_string']);
1115                 }
1116                 $values['date_created']=$bean->dbManager->getHelper()->massageValue(TimeDate::getInstance()->nowDb(), $fieldDefs['date_created'] );
1117                 $values['created_by']=$bean->dbManager->getHelper()->massageValue($current_user->id, $fieldDefs['created_by']);
1118
1119                 $sql .= "(".implode(",", array_keys($values)).") ";
1120                 $sql .= "VALUES(".implode(",", $values).")";
1121
1122         if ( $this->db->dbType == 'oci8' && $changes['data_type'] == 'text' ) {
1123             $sql .= " RETURNING before_value_text, after_value_text INTO :before_value_text, :after_value_text";
1124             $stmt = oci_parse($this->db->getDatabase(), $sql);
1125             $err = oci_error($this->db->getDatabase());
1126             if ($err != false){
1127                 $GLOBALS['log']->fatal($sql.">>".$err['code'].":".$err['message']);
1128                 return false;
1129             }
1130             $before_value_text_LOB = oci_new_descriptor($this->db->getDatabase(), OCI_D_LOB);
1131             oci_bind_by_name($stmt, ":before_value_text", $before_value_text_LOB, -1, OCI_B_CLOB);
1132             $after_value_text_LOB = oci_new_descriptor($this->db->getDatabase(), OCI_D_LOB);
1133             oci_bind_by_name($stmt, ":after_value_text", $after_value_text_LOB, -1, OCI_B_CLOB);
1134             oci_execute($stmt, OCI_DEFAULT);
1135             $err = oci_error($this->db->getDatabase());
1136             if ($err != false){
1137                 $GLOBALS['log']->fatal($sql.">>".$err['code'].":".$err['message']);
1138                 return false;
1139             }
1140             $before_value_text_LOB->save($changes['before']);
1141             $after_value_text_LOB->save($changes['after']);
1142             oci_commit($this->db->getDatabase());
1143             $before_value_text_LOB->free();
1144             $after_value_text_LOB->free();
1145             oci_free_statement($stmt);
1146         }
1147         else {
1148             $bean->db->query($sql);
1149         }
1150         }
1151
1152     /**
1153      * Uses the audit enabled fields array to find fields whose value has changed.
1154          * The before and after values are stored in the bean.
1155      *
1156      * @param object $bean Sugarbean instance
1157      * @return array
1158      */
1159         public function getDataChanges(
1160         SugarBean &$bean
1161         )
1162     {
1163         $changed_values=array();
1164                 $audit_fields=$bean->getAuditEnabledFieldDefinitions();
1165
1166                 if (is_array($audit_fields) and count($audit_fields) > 0) {
1167                         foreach ($audit_fields as $field=>$properties) {
1168
1169                                 if (!empty($bean->fetched_row) && array_key_exists($field, $bean->fetched_row)) {
1170
1171                                         if (isset($properties['type']))
1172                                                 $field_type=$properties['type'];
1173                                         else {
1174                                                 if (isset($properties['dbType']))
1175                                                         $field_type=$properties['dbType'];
1176                                                 else if(isset($properties['data_type']))
1177                                                         $field_type=$properties['data_type'];
1178                                                 else
1179                                                         $field_type=$properties['dbtype'];
1180                                         }
1181
1182                      // Bug # 44624 - check for currency type and cast to float
1183                      // this ensures proper matching for change log
1184                      if ( (strcmp($field_type,"currency")==0) ) {
1185                          $before_value=(float)$bean->fetched_row[$field];
1186                          $after_value=(float)$bean->$field;
1187                      } else {
1188                          $before_value=$bean->fetched_row[$field];
1189                          $after_value=$bean->$field;
1190                          }
1191
1192                                         //Because of bug #25078(sqlserver haven't 'date' type, trim extra "00:00:00" when insert into *_cstm table). so when we read the audit datetime field from sqlserver, we have to replace the extra "00:00:00" again.
1193                                         if(!empty($field_type) && $field_type == 'date'){
1194                                                 $before_value = from_db_convert($before_value , $field_type);
1195                                         }
1196                                         //if the type and values match, do nothing.
1197                                         if (!($this->_emptyValue($before_value,$field_type) && $this->_emptyValue($after_value,$field_type))) {
1198                                                 if (trim($before_value) !== trim($after_value)) {
1199                             // 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.
1200                                                         if (!($this->_isTypeNumber($field_type) && abs((trim($before_value)+0)-(trim($after_value)+0))<0.001)) {
1201                                                                 if (!($this->_isTypeBoolean($field_type) && ($this->_getBooleanValue($before_value)== $this->_getBooleanValue($after_value)))) {
1202                                    $changed_values[$field]=array('field_name'=>$field,
1203                                                                                 'data_type'=>$field_type,
1204                                                                                 'before'=>$before_value,
1205                                                                                 'after'=>$after_value);
1206                                                                 }
1207                                                         }
1208                                                 }
1209                                         }
1210                                 }
1211                         }
1212                 }
1213                 return $changed_values;
1214         }
1215
1216     /**
1217      * Function returns true is full-text indexing is available in the connected database.
1218      *
1219      * Default value is false.
1220      *
1221      * @param  string $dbname
1222      * @return bool
1223      */
1224         abstract protected function full_text_indexing_enabled(
1225         $dbname = null
1226         );
1227
1228     /**
1229      * Quotes a string for storing in the database
1230      *
1231      * Return value will be surrounded by quotes
1232      *
1233      * @param  string $string
1234      * @return string
1235      */
1236     public function quote(
1237         $string
1238         )
1239     {
1240         return "'".$this->db->quote($string)."'";
1241     }
1242
1243     /**
1244      * Quotes a string for storing in the database
1245      *
1246      * Return value will be not surrounded by quotes
1247      *
1248      * @param  string $string
1249      * @return string
1250      */
1251     public function escape_quote(
1252         $string
1253         )
1254     {
1255         return $this->db->quote($string);
1256     }
1257
1258     /**
1259      * Returns definitions of all indies for passed table.
1260      *
1261      * return will is a multi-dimensional array that
1262      * categorizes the index definition by types, unique, primary and index.
1263      * <code>
1264      * <?php
1265      * array(
1266      *       'index1'=> array (
1267      *           'name'   => 'index1',
1268      *           'type'   => 'primary',
1269      *           'fields' => array('field1','field2')
1270      *           )
1271      *       )
1272      * ?>
1273      * </code>
1274      * This format is similar to how indicies are defined in vardef file.
1275      *
1276      * @param  string $tablename
1277      * @return array
1278      */
1279     abstract public function get_indices(
1280         $tablename
1281         );
1282
1283     /**
1284      * Returns definitions of all indies for passed table.
1285      *
1286      * return will is a multi-dimensional array that
1287      * categorizes the index definition by types, unique, primary and index.
1288      * <code>
1289      * <?php
1290      * array(
1291      *       'field1'=> array (
1292      *           'name'   => 'field1',
1293      *           'type'   => 'varchar',
1294      *           'len' => '200'
1295      *           )
1296      *       )
1297      * ?>
1298      * </code>
1299      * This format is similar to how indicies are defined in vardef file.
1300      *
1301      * @param  string $tablename
1302      * @return array
1303      */
1304     abstract public function get_columns(
1305         $tablename
1306         );
1307
1308     /**
1309      * Generates alter constraint statement given a table name and vardef definition.
1310      *
1311      * Supports both adding and droping a constraint.
1312      *
1313      * @param  string $table     tablename
1314      * @param  array  $defintion field definition
1315      * @param  bool   $drop      true if we are dropping the constraint, false if we are adding it
1316      * @return string SQL statement
1317      */
1318     abstract public function add_drop_constraint(
1319         $table,
1320         $definition,
1321         $drop = false);
1322
1323     /**
1324      * Renames an index definition
1325      *
1326      * @param  array  $old_definition
1327      * @param  array  $new_definition
1328      * @param  string $tablename
1329      * @return string SQL statement
1330      */
1331     public function rename_index(
1332         $old_definition,
1333         $new_definition,
1334         $table_name
1335         )
1336     {
1337         $ret_commands   = array();
1338         $ret_commands[] = $this->add_drop_constraint($table_name,$old_definition,true);
1339         $ret_commands[] = $this->add_drop_constraint($table_name,$new_definition);
1340
1341         return $ret_commands;
1342     }
1343
1344     /**
1345      * Returns the number of columns in a table
1346      *
1347      * @param  string $table_name
1348      * @return int
1349      */
1350     abstract public function number_of_columns(
1351         $table_name
1352         );
1353
1354     protected function _isTypeBoolean(
1355         $type
1356         )
1357     {
1358         switch ($type) {
1359         case 'bool':
1360             return true;
1361         }
1362
1363         return false;
1364     }
1365
1366     protected function _getBooleanValue(
1367         $val
1368         )
1369     {
1370         //need to put the === sign here otherwise true == 'non empty string'
1371         if (empty($val) or $val==='off')
1372             return false;
1373
1374         return true;
1375     }
1376
1377     protected function _isTypeNumber(
1378         $type
1379         )
1380     {
1381         switch ($type) {
1382         case 'decimal':
1383         case 'int':
1384         case 'double':
1385         case 'float':
1386         case 'uint':
1387         case 'ulong':
1388         case 'long':
1389         case 'short':
1390         case 'currency':
1391             return true;
1392         }
1393         return false;
1394     }
1395
1396     /**
1397      * return true if the value if empty
1398      */
1399     protected function _emptyValue(
1400         $val,
1401         $type
1402         )
1403     {
1404         if (empty($val))
1405             return true;
1406
1407         switch ($type) {
1408         case 'decimal':
1409         case 'int':
1410         case 'double':
1411         case 'float':
1412         case 'uint':
1413         case 'ulong':
1414         case 'long':
1415         case 'short':
1416             if ($val == 0)
1417                 return true;
1418             return false;
1419         case 'date':
1420             if ($val == '0000-00-00')
1421                 return true;
1422             if ($val == 'NULL')
1423                 return true;
1424             return false;
1425         }
1426
1427         return false;
1428     }
1429 }
1430 ?>