PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/include/database/DBHelper.php

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