]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/database/DBHelper.php
Release 6.3.0beta1
[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 static $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     /**
562      * Returns the defintion for a single column
563      *
564      * @param  array  $fieldDef
565      * @param  bool   $ignoreRequired  Optional, true if we should ignor this being a required field
566      * @param  string $table           Optional, table name
567      * @param  bool   $return_as_array Optional, true if we should return the result as an array instead of sql
568      * @return string or array if $return_as_array is true
569      */
570         protected function oneColumnSQLRep(
571         $fieldDef,
572         $ignoreRequired = false,
573         $table = '',
574         $return_as_array = false
575         )
576     {
577
578         $name = $fieldDef['name'];
579         $type = $this->getFieldType($fieldDef);
580         $colType = $this->getColumnType($type, $name, $table);
581
582         if (( $colType == 'nvarchar'
583                                 or $colType == 'nchar'
584                                 or $colType == 'varchar'
585                                 or $colType == 'char'
586                                 or $colType == 'varchar2') ) {
587             if( !empty($fieldDef['len']))
588                 $colType .= "(".$fieldDef['len'].")";
589             else
590                 $colType .= "(255)";
591         }
592        if($colType == 'decimal' || $colType == 'float'){
593                 if(!empty($fieldDef     ['len'])){
594                         if(!empty($fieldDef['precision']) && is_numeric($fieldDef['precision']))
595                                 if(strpos($fieldDef     ['len'],',') === false){
596                             $colType .= "(".$fieldDef['len'].",".$fieldDef['precision'].")";
597                                 }else{
598                             $colType .= "(".$fieldDef['len'].")";
599                                 }
600                         else
601                             $colType .= "(".$fieldDef['len'].")";
602                 }
603        }
604
605
606         if (isset($fieldDef['default']) && strlen($fieldDef['default']) > 0)
607             $default = " DEFAULT '".$fieldDef['default']."'";
608         elseif (!isset($default) && $type == 'bool')
609             $default = " DEFAULT 0 ";
610         elseif (!isset($default))
611             $default = '';
612
613         $auto_increment = '';
614         if(!empty($fieldDef['auto_increment']) && $fieldDef['auto_increment'])
615                 $auto_increment = $this->setAutoIncrement($table , $fieldDef['name']);
616
617         $required = 'NULL';  // MySQL defaults to NULL, SQL Server defaults to NOT NULL -- must specify
618         //Starting in 6.0, only ID and auto_increment fields will be NOT NULL in the DB.
619         if ((empty($fieldDef['isnull']) || strtolower($fieldDef['isnull']) == 'false') &&
620                     (!empty($auto_increment) || $name == 'id' || ($fieldDef['type'] == 'id' && !empty($fieldDef['required'])))) {
621             $required =  "NOT NULL";
622         }
623         // If the field is marked both required & isnull=>false - alwqys make it not null
624         // Use this to ensure primary key fields never defined as null
625         if(isset($fieldDef['isnull']) && (strtolower($fieldDef['isnull']) == 'false' || $fieldDef['isnull'] === false)
626             && !empty($fieldDef['required'])) {
627             $required =  "NOT NULL";
628         }
629                 if ($ignoreRequired)
630             $required = "";
631
632         if ( $return_as_array )
633             return array(
634                 'name' => $name,
635                 'colType' => $colType,
636                 'default' => $default,
637                 'required' => $required,
638                 'auto_increment' => $auto_increment,
639                 'full' => "$name $colType $default $required $auto_increment",
640                 );
641             else
642                 return "$name $colType $default $required $auto_increment";
643
644         }
645
646     /**
647      * Returns SQL defintions for all columns in a table
648      *
649      * @param  array  $fieldDefs
650      * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
651      * @param  string $tablename      Optional, table name
652      * @return string SQL column definitions
653      */
654         protected function columnSQLRep(
655         $fieldDefs,
656         $ignoreRequired = false,
657         $tablename
658         )
659     {
660                 $columns = array();
661
662                 if ($this->isFieldArray($fieldDefs)) {
663                         foreach ($fieldDefs as $fieldDef) {
664                                 if(!isset($fieldDef['source']) || $fieldDef['source'] == 'db') {
665                                         $columns[] = $this->oneColumnSQLRep($fieldDef,false, $tablename);
666                                 }
667                         }
668                         $columns = implode(",", $columns);
669                 }
670                 else {
671                         $columns = $this->oneColumnSQLRep($fieldDefs,$ignoreRequired, $tablename);
672                 }
673
674                 return $columns;
675         }
676
677         /**
678      * Returns the next value for an auto increment
679      *
680      * @param  string $table tablename
681      * @param  string $field_name
682      * @return string
683      */
684         public function getAutoIncrement(
685         $table,
686         $field_name
687         )
688     {
689                 return "";
690         }
691
692         /**
693      * Returns the sql for the next value in a sequence
694      *
695      * @param  string $table tablename
696      * @param  string $field_name
697      * @return string
698      */
699     public function getAutoIncrementSQL(
700         $table,
701         $field_name
702         )
703     {
704         return "";
705     }
706
707
708
709         /**
710      * Either creates an auto increment through queries or returns sql for auto increment
711      * that can be appended to the end of column defination (mysql)
712      *
713      * @param  string $table tablename
714      * @param  string $field_name
715      * @return string
716      */
717         protected function setAutoIncrement(
718         $table,
719         $field_name
720         )
721     {
722         $this->deleteAutoIncrement($table, $field_name);
723         return "";
724         }
725
726
727     /**
728      * Sets the next auto-increment value of a column to a specific value.
729      *
730      * @param  string $table tablename
731      * @param  string $field_name
732      */
733     public function setAutoIncrementStart(
734         $table,
735         $field_name,
736         $start_value
737         )
738     {
739         return "";
740     }
741
742         /**
743      * Deletes an auto increment (for oracle not mysql)
744      *
745      * @param string $table tablename
746      * @param string $field_name
747      */
748         public function deleteAutoIncrement(
749         $table,
750         $field_name
751         )
752     {
753         return;
754         }
755
756         /**
757      * Generates the SQL for changing columns
758      *
759      * @param string $tablename
760      * @param array  $fieldDefs
761      * @param string $action
762      * @param bool   $ignoreRequired Optional, true if we should ignor this being a required field
763          */
764         abstract protected function changeColumnSQL(
765         $tablename,
766         $fieldDefs,
767         $action,
768         $ignoreRequired = false);
769
770     /**
771      * This method generates sql for adding a column to table identified by field def.
772      *
773      * @param  string $tablename
774      * @param  array  $fieldDefs
775      * @return string SQL statement
776      */
777         public function addColumnSQL(
778         $tablename,
779         $fieldDefs
780         )
781     {
782        return $this->changeColumnSQL($tablename, $fieldDefs, 'add');
783         }
784
785     /**
786      * This method genrates sql for altering old column identified by oldFieldDef to new fieldDef.
787      *
788      * @param  string $tablename
789      * @param  array  $newFieldDefs
790      * @param  bool   $ignoreRequired Optional, true if we should ignor this being a required field
791      * @return string SQL statement
792      */
793         public function alterColumnSQL(
794         $tablename,
795         $newFieldDefs,
796         $ignorerequired = false
797         )
798     {
799         return $this->changeColumnSQL($tablename, $newFieldDefs, 'modify', $ignorerequired);
800     }
801
802     /**
803      * Generates SQL for dropping a table.
804      *
805      * @param  object $bean Sugarbean instance
806      * @return string SQL statement
807      */
808         public function dropTableSQL(
809         SugarBean $bean
810         )
811     {
812                 return $this->dropTableNameSQL($bean->getTableName());
813         }
814
815         /**
816      * Generates SQL for dropping a table.
817      *
818      * @param  string $name table name
819      * @return string SQL statement
820      */
821         public function dropTableNameSQL(
822         $name
823         )
824     {
825                 return "drop table if exists ".$name;
826         }
827
828     /**
829      * This method generates sql that deletes a column identified by fieldDef.
830      *
831      * @param  object $bean      Sugarbean instance
832      * @param  array  $fieldDefs
833      * @return string SQL statement
834      */
835         public function deleteColumnSQL(
836         SugarBean $bean,
837         $fieldDefs
838         )
839     {
840         if ($this->isFieldArray($fieldDefs))
841             foreach ($fieldDefs as $fieldDef)
842                 $columns[] = $fieldDef['name'];
843         else
844             $columns[] = $fieldDefs['name'];
845
846         return "alter table ".$bean->getTableName()." drop (".implode(", ", $columns).")";
847         }
848
849     /**
850      * This method generates sql that drops a column identified by fieldDef.
851      * Designed to work like the other addColumnSQL() and alterColumnSQL() functions
852      *
853      * @param  string $tablename
854      * @param  array  $fieldDefs
855      * @return string SQL statement
856      */
857         public function dropColumnSQL(
858         $tablename,
859         $fieldDefs
860         )
861     {
862         $sql = $this->changeColumnSQL(
863             $tablename,
864             $fieldDefs,
865             'drop'
866             );
867         return $sql;
868
869         }
870
871     /**
872      * Generates SQL for key statement for any bean identified by id.
873      *
874      * The passes array is an array of field definitions or a field definition
875      * itself. The keys generated will be either primary, foreign, unique, index
876      * or none at all depending on the setting of the "key" parameter of a field definition
877      *
878      * @param  array  $indices
879      * @param  bool   $alter_table
880      * @param  string $alter_action
881      * @return string SQL Statement
882      */
883     protected function keysSQL(
884         $indices,
885         $alter_table = false,
886         $alter_action = ''
887         )
888         {
889         return '';
890     }
891
892     /**
893      * Outputs a correct string for the sql statement according to value
894      *
895      * @param  mixed $val
896      * @param  array $fieldDef field definition
897      * @return mixed
898      */
899         public function massageValue(
900         $val,
901         $fieldDef
902         )
903     {
904         if ( strlen($val) <= 0 )
905             return "''";
906
907         $type = $this->getFieldType($fieldDef);
908
909         switch ($type) {
910         case 'int':
911         case 'double':
912         case 'float':
913         case 'uint':
914         case 'ulong':
915         case 'long':
916         case 'short':
917         case 'tinyint':
918         case 'bool':
919             return $val;
920             break;
921                 }
922
923         $qval = $this->quote($val);
924
925         switch ($type) {
926         case 'varchar':
927         case 'char':
928         case 'longtext':
929         case 'text':
930         case 'enum':
931         case 'multienum':
932         case 'html':
933         case 'blob':
934         case 'longblob':
935         case 'clob':
936         case 'id':
937         case 'datetime':
938             return $qval;
939             break;
940         case 'date':
941         case 'time':
942             return "$qval";
943             break;
944         }
945
946         return $val;
947         }
948
949     /**
950      * Massages the field defintions to fill in anything else the DB backend may add
951      *
952      * @param  array  $fieldDef
953      * @param  string $tablename
954      * @return array
955      */
956     public function massageFieldDef(
957         &$fieldDef,
958         $tablename
959         )
960     {
961         if ( !isset($fieldDef['dbType']) ) {
962             if ( isset($fieldDef['dbtype']) )
963                 $fieldDef['dbType'] = $fieldDef['dbtype'];
964             else
965                 $fieldDef['dbType'] = $fieldDef['type'];
966         }
967         $type = $this->getColumnType($fieldDef['dbType'],$fieldDef['name'],$tablename);
968         $matches = array();
969         preg_match_all("/(\w+)(?:\(([0-9]+,?[0-9]*)\)|)/i", $type, $matches);
970         if ( isset($matches[1][0]) )
971             $fieldDef['type'] = $matches[1][0];
972         if ( isset($matches[2][0]) && empty($fieldDef['len']) )
973             $fieldDef['len'] = $matches[2][0];
974         if ( !empty($fieldDef['precision']) && is_numeric($fieldDef['precision']) && !strstr($fieldDef['len'],',') )
975             $fieldDef['len'] .= ",{$fieldDef['precision']}";
976         if (isset($fieldDef['required']) && ($fieldDef['required'] == true
977                     || $fieldDef['required'] == '1'
978                     || $fieldDef['required'] == 1)
979                 || ($fieldDef['name'] == 'id' && !isset($fieldDef['required'])) )
980             $fieldDef['required'] = 'true';
981     }
982
983     /*
984      * Return a version of $proposed that can be used as a column name in any of our supported databases
985      * 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)
986      * @param string $name Proposed name for the column
987      * @param string $ensureUnique
988      * @return string Valid column name trimmed to right length and with invalid characters removed
989      */
990      public static function getValidDBName ($name, $ensureUnique = false, $type = 'column')
991     {
992         if(is_array($name))
993         {
994             $result = array();
995             foreach($name as $field)
996             {
997                 $result[] = self::getValidDBName($field, $ensureUnique, $type);
998             }
999         }else
1000         {
1001             // first strip any invalid characters - all but alphanumerics and -
1002             $name = preg_replace ( '/[^\w-]+/i', '', $name ) ;
1003             $len = strlen ( $name ) ;
1004             $result = $name;
1005             $maxLen = empty(self::$maxNameLengths[$type]) ? self::$maxNameLengths[$type]['column'] : self::$maxNameLengths[$type];
1006             if ($len <= $maxLen)
1007             {
1008                 return strtolower($name);
1009             }
1010             if ($ensureUnique)
1011             {
1012                 $md5str = md5($name);
1013                 $tail = substr ( $name, -11) ;
1014                 $temp = substr($md5str , strlen($md5str)-4 );
1015                 $result = substr ( $name, 0, 10) . $temp . $tail ;
1016             }
1017             else
1018             {
1019                 $result = substr ( $name, 0, 11) . substr ( $name, 11 - $maxLen);
1020             }
1021
1022             return strtolower ( $result ) ;
1023         }
1024     }
1025
1026     /**
1027      * Returns the valid type for a column given the type in fieldDef
1028      *
1029      * @param  string $type field type
1030      * @return string valid type for the given field
1031      */
1032     abstract public function getColumnType(
1033         $type,
1034         $name = '',
1035         $table = ''
1036         );
1037
1038     /**
1039      * Checks to see if passed array is truely an array of defitions
1040      *
1041      * Such an array may have type as a key but it will point to an array
1042      * for a true array of definitions an to a col type for a definition only
1043      *
1044      * @param  mixed $defArray
1045      * @return bool
1046      */
1047     public function isFieldArray(
1048         $defArray
1049         )
1050     {
1051         if ( !is_array($defArray) )
1052             return false;
1053
1054         if ( isset($defArray['type']) ){
1055             // type key exists. May be an array of defs or a simple definition
1056             $type = $defArray['type'];
1057             return is_array($type); // type is not an array => definition else array
1058         }
1059
1060         // type does not exist. Must be array of definitions
1061         return true;
1062     }
1063
1064     /**
1065      * returns true if the type can be mapped to a valid column type
1066      *
1067      * @param  string $type
1068      * @return bool
1069      */
1070     protected function validColumnType(
1071         $type
1072         )
1073     {
1074         $coltype = $this->getColumnType($type);
1075         return ($coltype) ? true : false;
1076     }
1077
1078     /**
1079      * Saves changes to module's audit table
1080      *
1081      * @param object $bean    Sugarbean instance
1082      * @param array  $changes changes
1083      * @see DBHelper::getDataChanges()
1084      */
1085     public function save_audit_records(SugarBean $bean, $changes)
1086         {
1087                 global $current_user;
1088                 $sql = "INSERT INTO ".$bean->get_audit_table_name();
1089                 //get field defs for the audit table.
1090                 require('metadata/audit_templateMetaData.php');
1091                 $fieldDefs = $dictionary['audit']['fields'];
1092
1093                 $values=array();
1094                 $values['id']=$this->massageValue(create_guid(), $fieldDefs['id']);
1095                 $values['parent_id']=$bean->dbManager->getHelper()->massageValue($bean->id, $fieldDefs['parent_id']);
1096                 $values['field_name']=$bean->dbManager->getHelper()->massageValue($changes['field_name'], $fieldDefs['field_name']);
1097                 $values['data_type']=$bean->dbManager->getHelper()->massageValue($changes['data_type'], $fieldDefs['data_type']);
1098                 if ($changes['data_type']=='text') {
1099                         $bean->fetched_row[$changes['field_name']]=$changes['after'];;
1100                         $values['before_value_text']=$bean->dbManager->getHelper()->massageValue($changes['before'], $fieldDefs['before_value_text']);
1101                         $values['after_value_text']=$bean->dbManager->getHelper()->massageValue($changes['after'], $fieldDefs['after_value_text']);
1102                 } else {
1103                         $bean->fetched_row[$changes['field_name']]=$changes['after'];;
1104                         $values['before_value_string']=$bean->dbManager->getHelper()->massageValue($changes['before'], $fieldDefs['before_value_string']);
1105                         $values['after_value_string']=$bean->dbManager->getHelper()->massageValue($changes['after'], $fieldDefs['after_value_string']);
1106                 }
1107                 $values['date_created']=$bean->dbManager->getHelper()->massageValue(TimeDate::getInstance()->nowDb(), $fieldDefs['date_created'] );
1108                 $values['created_by']=$bean->dbManager->getHelper()->massageValue($current_user->id, $fieldDefs['created_by']);
1109
1110                 $sql .= "(".implode(",", array_keys($values)).") ";
1111                 $sql .= "VALUES(".implode(",", $values).")";
1112
1113         if ( $this->db->dbType == 'oci8' && $changes['data_type'] == 'text' ) {
1114             $sql .= " RETURNING before_value_text, after_value_text INTO :before_value_text, :after_value_text";
1115             $stmt = oci_parse($this->db->getDatabase(), $sql);
1116             $err = oci_error($this->db->getDatabase());
1117             if ($err != false){
1118                 $GLOBALS['log']->fatal($sql.">>".$err['code'].":".$err['message']);
1119                 return false;
1120             }
1121             $before_value_text_LOB = oci_new_descriptor($this->db->getDatabase(), OCI_D_LOB);
1122             oci_bind_by_name($stmt, ":before_value_text", $before_value_text_LOB, -1, OCI_B_CLOB);
1123             $after_value_text_LOB = oci_new_descriptor($this->db->getDatabase(), OCI_D_LOB);
1124             oci_bind_by_name($stmt, ":after_value_text", $after_value_text_LOB, -1, OCI_B_CLOB);
1125             oci_execute($stmt, OCI_DEFAULT);
1126             $err = oci_error($this->db->getDatabase());
1127             if ($err != false){
1128                 $GLOBALS['log']->fatal($sql.">>".$err['code'].":".$err['message']);
1129                 return false;
1130             }
1131             $before_value_text_LOB->save($changes['before']);
1132             $after_value_text_LOB->save($changes['after']);
1133             oci_commit($this->db->getDatabase());
1134             $before_value_text_LOB->free();
1135             $after_value_text_LOB->free();
1136             oci_free_statement($stmt);
1137         }
1138         else {
1139             $bean->db->query($sql);
1140         }
1141         }
1142
1143     /**
1144      * Uses the audit enabled fields array to find fields whose value has changed.
1145          * The before and after values are stored in the bean.
1146      *
1147      * @param object $bean Sugarbean instance
1148      * @return array
1149      */
1150         public function getDataChanges(
1151         SugarBean &$bean
1152         )
1153     {
1154         $changed_values=array();
1155                 $audit_fields=$bean->getAuditEnabledFieldDefinitions();
1156
1157                 if (is_array($audit_fields) and count($audit_fields) > 0) {
1158                         foreach ($audit_fields as $field=>$properties) {
1159
1160                                 if (!empty($bean->fetched_row) && array_key_exists($field, $bean->fetched_row)) {
1161
1162                                         $before_value=$bean->fetched_row[$field];
1163                                         $after_value=$bean->$field;
1164                                         if (isset($properties['type']))
1165                                                 $field_type=$properties['type'];
1166                                         else {
1167                                                 if (isset($properties['dbType']))
1168                                                         $field_type=$properties['dbType'];
1169                                                 else if(isset($properties['data_type']))
1170                                                         $field_type=$properties['data_type'];
1171                                                 else
1172                                                         $field_type=$properties['dbtype'];
1173                                         }
1174
1175                                         //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.
1176                                         if(!empty($field_type) && $field_type == 'date'){
1177                                                 $before_value = from_db_convert($before_value , $field_type);
1178                                         }
1179                                         //if the type and values match, do nothing.
1180                                         if (!($this->_emptyValue($before_value,$field_type) && $this->_emptyValue($after_value,$field_type))) {
1181                                                 if (trim($before_value) !== trim($after_value)) {
1182                                                         if (!($this->_isTypeNumber($field_type) && (trim($before_value)+0) == (trim($after_value)+0))) {
1183                                                                 if (!($this->_isTypeBoolean($field_type) && ($this->_getBooleanValue($before_value)== $this->_getBooleanValue($after_value)))) {
1184                                                                         $changed_values[$field]=array('field_name'=>$field,
1185                                                                                 'data_type'=>$field_type,
1186                                                                                 'before'=>$before_value,
1187                                                                                 'after'=>$after_value);
1188                                                                 }
1189                                                         }
1190                                                 }
1191                                         }
1192                                 }
1193                         }
1194                 }
1195                 return $changed_values;
1196         }
1197
1198     /**
1199      * Function returns true is full-text indexing is available in the connected database.
1200      *
1201      * Default value is false.
1202      *
1203      * @param  string $dbname
1204      * @return bool
1205      */
1206         abstract protected function full_text_indexing_enabled(
1207         $dbname = null
1208         );
1209
1210     /**
1211      * Quotes a string for storing in the database
1212      *
1213      * Return value will be surrounded by quotes
1214      *
1215      * @param  string $string
1216      * @return string
1217      */
1218     public function quote(
1219         $string
1220         )
1221     {
1222         return "'".$this->db->quote($string)."'";
1223     }
1224
1225     /**
1226      * Quotes a string for storing in the database
1227      *
1228      * Return value will be not surrounded by quotes
1229      *
1230      * @param  string $string
1231      * @return string
1232      */
1233     public function escape_quote(
1234         $string
1235         )
1236     {
1237         return $this->db->quote($string);
1238     }
1239
1240     /**
1241      * Returns definitions of all indies for passed table.
1242      *
1243      * return will is a multi-dimensional array that
1244      * categorizes the index definition by types, unique, primary and index.
1245      * <code>
1246      * <?php
1247      * array(
1248      *       'index1'=> array (
1249      *           'name'   => 'index1',
1250      *           'type'   => 'primary',
1251      *           'fields' => array('field1','field2')
1252      *           )
1253      *       )
1254      * ?>
1255      * </code>
1256      * This format is similar to how indicies are defined in vardef file.
1257      *
1258      * @param  string $tablename
1259      * @return array
1260      */
1261     abstract public function get_indices(
1262         $tablename
1263         );
1264
1265     /**
1266      * Returns definitions of all indies for passed table.
1267      *
1268      * return will is a multi-dimensional array that
1269      * categorizes the index definition by types, unique, primary and index.
1270      * <code>
1271      * <?php
1272      * array(
1273      *       'field1'=> array (
1274      *           'name'   => 'field1',
1275      *           'type'   => 'varchar',
1276      *           'len' => '200'
1277      *           )
1278      *       )
1279      * ?>
1280      * </code>
1281      * This format is similar to how indicies are defined in vardef file.
1282      *
1283      * @param  string $tablename
1284      * @return array
1285      */
1286     abstract public function get_columns(
1287         $tablename
1288         );
1289
1290     /**
1291      * Generates alter constraint statement given a table name and vardef definition.
1292      *
1293      * Supports both adding and droping a constraint.
1294      *
1295      * @param  string $table     tablename
1296      * @param  array  $defintion field definition
1297      * @param  bool   $drop      true if we are dropping the constraint, false if we are adding it
1298      * @return string SQL statement
1299      */
1300     abstract public function add_drop_constraint(
1301         $table,
1302         $definition,
1303         $drop = false);
1304
1305     /**
1306      * Renames an index definition
1307      *
1308      * @param  array  $old_definition
1309      * @param  array  $new_definition
1310      * @param  string $tablename
1311      * @return string SQL statement
1312      */
1313     public function rename_index(
1314         $old_definition,
1315         $new_definition,
1316         $table_name
1317         )
1318     {
1319         $ret_commands   = array();
1320         $ret_commands[] = $this->add_drop_constraint($table_name,$old_definition,true);
1321         $ret_commands[] = $this->add_drop_constraint($table_name,$new_definition);
1322
1323         return $ret_commands;
1324     }
1325
1326     /**
1327      * Returns the number of columns in a table
1328      *
1329      * @param  string $table_name
1330      * @return int
1331      */
1332     abstract public function number_of_columns(
1333         $table_name
1334         );
1335
1336     protected function _isTypeBoolean(
1337         $type
1338         )
1339     {
1340         switch ($type) {
1341         case 'bool':
1342             return true;
1343         }
1344
1345         return false;
1346     }
1347
1348     protected function _getBooleanValue(
1349         $val
1350         )
1351     {
1352         //need to put the === sign here otherwise true == 'non empty string'
1353         if (empty($val) or $val==='off')
1354             return false;
1355
1356         return true;
1357     }
1358
1359     protected function _isTypeNumber(
1360         $type
1361         )
1362     {
1363         switch ($type) {
1364         case 'decimal':
1365         case 'int':
1366         case 'double':
1367         case 'float':
1368         case 'uint':
1369         case 'ulong':
1370         case 'long':
1371         case 'short':
1372             return true;
1373         }
1374         return false;
1375     }
1376
1377     /**
1378      * return true if the value if empty
1379      */
1380     protected function _emptyValue(
1381         $val,
1382         $type
1383         )
1384     {
1385         if (empty($val))
1386             return true;
1387
1388         switch ($type) {
1389         case 'decimal':
1390         case 'int':
1391         case 'double':
1392         case 'float':
1393         case 'uint':
1394         case 'ulong':
1395         case 'long':
1396         case 'short':
1397             if ($val == 0)
1398                 return true;
1399             return false;
1400         case 'date':
1401             if ($val == '0000-00-00')
1402                 return true;
1403             if ($val == 'NULL')
1404                 return true;
1405             return false;
1406         }
1407
1408         return false;
1409     }
1410 }
1411 ?>