PageRenderTime 42ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/include/database/DBManager.php

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