/include/database/DBManager.php

https://bitbucket.org/cviolette/sugarcrm · PHP · 3829 lines · 1910 code · 339 blank · 1580 comment · 518 complexity · 9a60fc7b5ac377121f8817b617c566e9 MD5 · raw file

Large files are truncated click here to view the full file

  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…