PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/include/database/MssqlManager.php

https://github.com/vincentamari/SuperSweetAdmin
PHP | 1309 lines | 858 code | 124 blank | 327 comment | 153 complexity | a3532faae8ed3b2fb486207a91c05b78 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, AGPL-3.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
  3. /*********************************************************************************
  4. * SugarCRM is a customer relationship management program developed by
  5. * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
  6. *
  7. * This program is free software; you can redistribute it and/or modify it under
  8. * the terms of the GNU Affero General Public License version 3 as published by the
  9. * Free Software Foundation with the addition of the following permission added
  10. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  11. * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
  12. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  13. *
  14. * This program is distributed in the hope that it will be useful, but WITHOUT
  15. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  17. * details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License along with
  20. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  21. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  22. * 02110-1301 USA.
  23. *
  24. * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
  25. * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
  26. *
  27. * The interactive user interfaces in modified source and object code versions
  28. * of this program must display Appropriate Legal Notices, as required under
  29. * Section 5 of the GNU Affero General Public License version 3.
  30. *
  31. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  32. * these Appropriate Legal Notices must retain the display of the "Powered by
  33. * SugarCRM" logo. If the display of the logo is not reasonably feasible for
  34. * technical reasons, the Appropriate Legal Notices must display the words
  35. * "Powered by SugarCRM".
  36. ********************************************************************************/
  37. /*********************************************************************************
  38. * Description: This file 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. class MssqlManager extends DBManager
  89. {
  90. /**
  91. * @see DBManager::$dbType
  92. */
  93. public $dbType = 'mssql';
  94. /**
  95. * @see DBManager::$backendFunctions
  96. */
  97. protected $backendFunctions = array(
  98. 'free_result' => 'mssql_free_result',
  99. 'close' => 'mssql_close',
  100. 'row_count' => 'mssql_num_rows'
  101. );
  102. /**
  103. * @see DBManager::connect()
  104. */
  105. public function connect(
  106. array $configOptions = null,
  107. $dieOnError = false
  108. )
  109. {
  110. global $sugar_config;
  111. if (is_null($configOptions))
  112. $configOptions = $sugar_config['dbconfig'];
  113. //SET DATEFORMAT to 'YYYY-MM-DD''
  114. ini_set('mssql.datetimeconvert', '0');
  115. //set the text size and textlimit to max number so that blob columns are not truncated
  116. ini_set('mssql.textlimit','2147483647');
  117. ini_set('mssql.textsize','2147483647');
  118. //set the connections parameters
  119. $connect_param = '';
  120. $configOptions['db_host_instance'] = trim($configOptions['db_host_instance']);
  121. if (empty($configOptions['db_host_instance']))
  122. $connect_param = $configOptions['db_host_name'];
  123. else
  124. $connect_param = $configOptions['db_host_name']."\\".$configOptions['db_host_instance'];
  125. //create persistent connection
  126. if ($sugar_config['dbconfigoption']['persistent'] == true) {
  127. $this->database =@mssql_pconnect(
  128. $connect_param ,
  129. $configOptions['db_user_name'],
  130. $configOptions['db_password']
  131. );
  132. }
  133. //if no persistent connection created, then create regular connection
  134. if(!$this->database){
  135. $this->database = mssql_connect(
  136. $connect_param ,
  137. $configOptions['db_user_name'],
  138. $configOptions['db_password']
  139. );
  140. if(!$this->database){
  141. $GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name'].
  142. " as ".$configOptions['db_user_name'].".");
  143. sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
  144. }
  145. if($this->database && $sugar_config['dbconfigoption']['persistent'] == true){
  146. $_SESSION['administrator_error'] = "<B>Severe Performance Degradation: Persistent Database Connections "
  147. . "not working. Please set \$sugar_config['dbconfigoption']['persistent'] to false in your "
  148. . "config.php file</B>";
  149. }
  150. }
  151. //make sure connection exists
  152. if(!$this->database){
  153. sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
  154. }
  155. //select database
  156. //Adding sleep and retry for mssql connection. We have come across scenarios when
  157. //an error is thrown.' Unable to select database'. Following will try to connect to
  158. //mssql db maximum number of 5 times at the interval of .2 second. If can not connect
  159. //it will throw an Unable to select database message.
  160. if(!@mssql_select_db($configOptions['db_name'], $this->database)){
  161. $connected = false;
  162. for($i=0;$i<5;$i++){
  163. usleep(200000);
  164. if(@mssql_select_db($configOptions['db_name'], $this->database)){
  165. $connected = true;
  166. break;
  167. }
  168. }
  169. if(!$connected){
  170. $GLOBALS['log']->fatal( "Unable to select database {$configOptions['db_name']}");
  171. sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
  172. }
  173. }
  174. if($this->checkError('Could Not Connect', $dieOnError))
  175. $GLOBALS['log']->info("connected to db");
  176. $GLOBALS['log']->info("Connect:".$this->database);
  177. }
  178. /**
  179. * @see DBManager::version()
  180. */
  181. public function version()
  182. {
  183. return $this->getOne("SELECT @@VERSION as version");
  184. }
  185. /**
  186. * @see DBManager::checkError()
  187. */
  188. public function checkError(
  189. $msg = '',
  190. $dieOnError = false
  191. )
  192. {
  193. if (parent::checkError($msg, $dieOnError))
  194. return true;
  195. $sqlmsg = mssql_get_last_message();
  196. $sqlpos = strpos($sqlmsg, 'Changed database context to');
  197. $sqlpos2 = strpos($sqlmsg, 'Warning:');
  198. $sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
  199. if ( $sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false )
  200. $sqlmsg = ''; // empty out sqlmsg if its either of the two error messages described above
  201. else {
  202. global $app_strings;
  203. //ERR_MSSQL_DB_CONTEXT: localized version of 'Changed database context to' message
  204. if (empty($app_strings) or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])) {
  205. //ignore the message from sql-server if $app_strings array is empty. This will happen
  206. //only if connection if made before languge is set.
  207. $GLOBALS['log']->debug("Ignoring this database message: " . $sqlmsg);
  208. $sqlmsg = '';
  209. }
  210. else {
  211. $sqlpos = strpos($sqlmsg, $app_strings['ERR_MSSQL_DB_CONTEXT']);
  212. if ( $sqlpos !== false )
  213. $sqlmsg = '';
  214. }
  215. }
  216. if ( strlen($sqlmsg) > 2 ) {
  217. $GLOBALS['log']->fatal("$msg: SQL Server error: " . $sqlmsg);
  218. return true;
  219. }
  220. return false;
  221. }
  222. /**
  223. * @see DBManager::query()
  224. */
  225. public function query(
  226. $sql,
  227. $dieOnError = false,
  228. $msg = '',
  229. $suppress = false
  230. )
  231. {
  232. // Flag if there are odd number of single quotes
  233. if ((substr_count($sql, "'") & 1))
  234. $GLOBALS['log']->error("SQL statement[" . $sql . "] has odd number of single quotes.");
  235. $this->countQuery($sql);
  236. $GLOBALS['log']->info('Query:' . $sql);
  237. $this->checkConnection();
  238. $this->query_time = microtime(true);
  239. // Bug 34892 - Clear out previous error message by checking the @@ERROR global variable
  240. $errorNumberHandle = mssql_query("SELECT @@ERROR",$this->database);
  241. $errorNumber = array_shift(mssql_fetch_row($errorNumberHandle));
  242. if ($suppress) {
  243. }
  244. else {
  245. $result = @mssql_query($sql, $this->database);
  246. }
  247. if (!$result) {
  248. // awu Bug 10657: ignoring mssql error message 'Changed database context to' - an intermittent
  249. // and difficult to reproduce error. The message is only a warning, and does
  250. // not affect the functionality of the query
  251. $sqlmsg = mssql_get_last_message();
  252. $sqlpos = strpos($sqlmsg, 'Changed database context to');
  253. $sqlpos2 = strpos($sqlmsg, 'Warning:');
  254. $sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
  255. if ($sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false) // if sqlmsg has 'Changed database context to', just log it
  256. $GLOBALS['log']->debug($sqlmsg . ": " . $sql );
  257. else {
  258. $GLOBALS['log']->fatal($sqlmsg . ": " . $sql );
  259. if($dieOnError)
  260. sugar_die('SQL Error : ' . $sqlmsg);
  261. else
  262. echo 'SQL Error : ' . $sqlmsg;
  263. }
  264. }
  265. $this->lastmysqlrow = -1;
  266. $this->query_time = microtime(true) - $this->query_time;
  267. $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
  268. $this->checkError($msg.' Query Failed: ' . $sql, $dieOnError);
  269. return $result;
  270. }
  271. /**
  272. * This function take in the sql for a union query, the start and offset,
  273. * and wraps it around an "mssql friendly" limit query
  274. *
  275. * @param string $sql
  276. * @param int $start record to start at
  277. * @param int $count number of records to retrieve
  278. * @return string SQL statement
  279. */
  280. private function handleUnionLimitQuery(
  281. $sql,
  282. $start,
  283. $count
  284. )
  285. {
  286. //set the start to 0, no negs
  287. if ($start < 0)
  288. $start=0;
  289. $GLOBALS['log']->debug(print_r(func_get_args(),true));
  290. $this->lastsql = $sql;
  291. //change the casing to lower for easier string comparison, and trim whitespaces
  292. $sql = strtolower(trim($sql)) ;
  293. //set default sql
  294. $limitUnionSQL = $sql;
  295. $order_by_str = 'order by';
  296. //make array of order by's. substring approach was proving too inconsistent
  297. $orderByArray = explode($order_by_str, $sql);
  298. $unionOrderBy = '';
  299. $rowNumOrderBy = '';
  300. //count the number of array elements
  301. $unionOrderByCount = count($orderByArray);
  302. $arr_count = 0;
  303. //process if there are elements
  304. if ($unionOrderByCount){
  305. //we really want the last ordery by, so reconstruct string
  306. //adding a 1 to count, as we dont wish to process the last element
  307. $unionsql = '';
  308. while ($unionOrderByCount>$arr_count+1) {
  309. $unionsql .= $orderByArray[$arr_count];
  310. $arr_count = $arr_count+1;
  311. //add an "order by" string back if we are coming into loop again
  312. //remember they were taken out when array was created
  313. if ($unionOrderByCount>$arr_count+1) {
  314. $unionsql .= "order by";
  315. }
  316. }
  317. //grab the last order by element, set both order by's'
  318. $unionOrderBy = $orderByArray[$arr_count];
  319. $rowNumOrderBy = $unionOrderBy;
  320. //if last element contains a "select", then this is part of the union query,
  321. //and there is no order by to use
  322. if (strpos($unionOrderBy, "select")) {
  323. $unionsql = $sql;
  324. //with no guidance on what to use for required order by in rownumber function,
  325. //resort to using name column.
  326. $rowNumOrderBy = 'id';
  327. $unionOrderBy = "";
  328. }
  329. }
  330. else {
  331. //there are no order by elements, so just pass back string
  332. $unionsql = $sql;
  333. //with no guidance on what to use for required order by in rownumber function,
  334. //resort to using name column.
  335. $rowNumOrderBy = 'id';
  336. $unionOrderBy = '';
  337. }
  338. //Unions need the column name being sorted on to match acroos all queries in Union statement
  339. //so we do not want to strip the alias like in other queries. Just add the "order by" string and
  340. //pass column name as is
  341. if ($unionOrderBy != '') {
  342. $unionOrderBy = ' order by ' . $unionOrderBy;
  343. }
  344. //if start is 0, then just use a top query
  345. if($start == 0) {
  346. $limitUnionSQL = "select top $count * from (" .$unionsql .") as top_count ".$unionOrderBy;
  347. }
  348. else {
  349. //if start is more than 0, then use top query in conjunction
  350. //with rownumber() function to create limit query.
  351. $limitUnionSQL = "select top $count * from( select ROW_NUMBER() OVER ( order by "
  352. .$rowNumOrderBy.") AS row_number, * from ("
  353. .$unionsql .") As numbered) "
  354. . "As top_count_limit WHERE row_number > $start "
  355. .$unionOrderBy;
  356. }
  357. return $limitUnionSQL;
  358. }
  359. /**
  360. * @see DBManager::limitQuery()
  361. */
  362. public function limitQuery(
  363. $sql,
  364. $start,
  365. $count,
  366. $dieOnError = false,
  367. $msg = '')
  368. {
  369. $newSQL = $sql;
  370. $distinctSQLARRAY = array();
  371. if (strpos($sql, "UNION") && !preg_match("/(\')(UNION).?(\')/i", $sql))
  372. $newSQL = $this->handleUnionLimitQuery($sql,$start,$count);
  373. else {
  374. if ($start < 0)
  375. $start = 0;
  376. $GLOBALS['log']->debug(print_r(func_get_args(),true));
  377. $this->lastsql = $sql;
  378. $matches = array();
  379. preg_match('/^(.*SELECT )(.*?FROM.*WHERE)(.*)$/isU',$sql, $matches);
  380. if (!empty($matches[3])) {
  381. if ($start == 0) {
  382. $match_two = strtolower($matches[2]);
  383. if (!strpos($match_two, "distinct")> 0 && strpos($match_two, "distinct") !==0) {
  384. //proceed as normal
  385. $newSQL = $matches[1] . " TOP $count " . $matches[2] . $matches[3];
  386. }
  387. else {
  388. $distinct_o = strpos($match_two, "distinct");
  389. $up_to_distinct_str = substr($match_two, 0, $distinct_o);
  390. //check to see if the distinct is within a function, if so, then proceed as normal
  391. if (strpos($up_to_distinct_str,"(")) {
  392. //proceed as normal
  393. $newSQL = $matches[1] . " TOP $count " . $matches[2] . $matches[3];
  394. }
  395. else {
  396. //if distinct is not within a function, then parse
  397. //string contains distinct clause, "TOP needs to come after Distinct"
  398. //get position of distinct
  399. $match_zero = strtolower($matches[0]);
  400. $distinct_pos = strpos($match_zero , "distinct");
  401. //get position of where
  402. $where_pos = strpos($match_zero, "where");
  403. //parse through string
  404. $beg = substr($matches[0], 0, $distinct_pos+9 );
  405. $mid = substr($matches[0], strlen($beg), ($where_pos+5) - (strlen($beg)));
  406. $end = substr($matches[0], strlen($beg) + strlen($mid) );
  407. //repopulate matches array
  408. $matches[1] = $beg; $matches[2] = $mid; $matches[3] = $end;
  409. $newSQL = $matches[1] . " TOP $count " . $matches[2] . $matches[3];
  410. }
  411. }
  412. }
  413. else {
  414. $orderByMatch = array();
  415. preg_match('/^(.*)(ORDER BY)(.*)$/is',$matches[3], $orderByMatch);
  416. //if there is a distinct clause, parse sql string as we will have to insert the rownumber
  417. //for paging, AFTER the distinct clause
  418. $hasDistinct = strpos(strtolower($matches[0]), "distinct");
  419. if ($hasDistinct) {
  420. $matches_sql = strtolower($matches[0]);
  421. //remove reference to distinct and select keywords, as we will use a group by instead
  422. //we need to use group by because we are introducing rownumber column which would make every row unique
  423. //take out the select and distinct from string so we can reuse in group by
  424. $dist_str = ' distinct ';
  425. $distinct_pos = strpos($matches_sql, $dist_str);
  426. $matches_sql = substr($matches_sql,$distinct_pos+ strlen($dist_str));
  427. //get the position of where and from for further processing
  428. $from_pos = strpos($matches_sql , " from ");
  429. $where_pos = strpos($matches_sql, "where");
  430. //split the sql into a string before and after the from clause
  431. //we will use the columns being selected to construct the group by clause
  432. if ($from_pos>0 ) {
  433. $distinctSQLARRAY[0] = substr($matches_sql,0, $from_pos+1);
  434. $distinctSQLARRAY[1] = substr($matches_sql,$from_pos+1);
  435. //get position of order by (if it exists) so we can strip it from the string
  436. $ob_pos = strpos($distinctSQLARRAY[1], "order by");
  437. if ($ob_pos) {
  438. $distinctSQLARRAY[1] = substr($distinctSQLARRAY[1],0,$ob_pos);
  439. }
  440. }
  441. //place group by string into array
  442. $grpByArr = explode(',', $distinctSQLARRAY[0]);
  443. $grpByStr = '';
  444. $first = true;
  445. //remove the aliases for each group by element, sql server doesnt like these in group by.
  446. foreach ($grpByArr as $gb) {
  447. $gb = trim($gb);
  448. //remove outer reference if they exist
  449. if (strpos($gb,"'")!==false){
  450. continue;
  451. }
  452. //if there is a space, then an alias exists, remove alias
  453. if (strpos($gb,' ')){
  454. $gb = substr( $gb, 0,strpos($gb,' '));
  455. }
  456. //if resulting string is not empty then add to new group by string
  457. if (!empty($gb)) {
  458. if ($first) {
  459. $grpByStr .= " $gb";
  460. $first = false;
  461. }
  462. else {
  463. $grpByStr .= ", $gb";
  464. }
  465. }
  466. }
  467. }
  468. if (!empty($orderByMatch[3])) {
  469. //if there is a distinct clause, form query with rownumber after distinct
  470. if ($hasDistinct) {
  471. $newSQL = "SELECT TOP $count * FROM
  472. (
  473. SELECT ROW_NUMBER()
  474. OVER (ORDER BY ".$this->returnOrderBy($sql, $orderByMatch[3]).") AS row_number,
  475. count(*) counter, " . $distinctSQLARRAY[0] . "
  476. " . $distinctSQLARRAY[1] . "
  477. group by " . $grpByStr . "
  478. ) AS a
  479. WHERE row_number > $start";
  480. }
  481. else {
  482. $newSQL = "SELECT TOP $count * FROM
  483. (
  484. " . $matches[1] . " ROW_NUMBER()
  485. OVER (ORDER BY " . $this->returnOrderBy($sql, $orderByMatch[3]) . ") AS row_number,
  486. " . $matches[2] . $orderByMatch[1]. "
  487. ) AS a
  488. WHERE row_number > $start";
  489. }
  490. }else{
  491. //bug: 22231 Records in campaigns' subpanel may not come from
  492. //table of $_REQUEST['module']. Get it directly from query
  493. $upperQuery = strtoupper($matches[2]);
  494. if (!strpos($upperQuery,"JOIN")){
  495. $from_pos = strpos($upperQuery , "FROM") + 4;
  496. $where_pos = strpos($upperQuery, "WHERE");
  497. $tablename = trim(substr($upperQuery,$from_pos, $where_pos - $from_pos));
  498. }else{
  499. $tablename = $this->getTableNameFromModuleName($_REQUEST['module'],$sql);
  500. }
  501. //if there is a distinct clause, form query with rownumber after distinct
  502. if ($hasDistinct) {
  503. $newSQL = "SELECT TOP $count * FROM
  504. (
  505. SELECT ROW_NUMBER() OVER (ORDER BY ".$tablename.".id) AS row_number, count(*) counter, " . $distinctSQLARRAY[0] . "
  506. " . $distinctSQLARRAY[1] . "
  507. group by " . $grpByStr . "
  508. )
  509. AS a
  510. WHERE row_number > $start";
  511. }
  512. else {
  513. $newSQL = "SELECT TOP $count * FROM
  514. (
  515. " . $matches[1] . " ROW_NUMBER() OVER (ORDER BY ".$tablename.".id) AS row_number, " . $matches[2] . $matches[3]. "
  516. )
  517. AS a
  518. WHERE row_number > $start";
  519. }
  520. }
  521. }
  522. }
  523. }
  524. $GLOBALS['log']->debug('Limit Query: ' . $newSQL);
  525. $result = $this->query($newSQL, $dieOnError, $msg);
  526. $this->dump_slow_queries($newSQL);
  527. return $result;
  528. }
  529. /**
  530. * Searches for begginning and ending characters. It places contents into
  531. * an array and replaces contents in original string. This is used to account for use of
  532. * nested functions while aliasing column names
  533. *
  534. * @param string $p_sql SQL statement
  535. * @param string $strip_beg Beginning character
  536. * @param string $strip_end Ending character
  537. * @param string $patt Optional, pattern to
  538. */
  539. private function removePatternFromSQL(
  540. $p_sql,
  541. $strip_beg,
  542. $strip_end,
  543. $patt = 'patt')
  544. {
  545. //strip all single quotes out
  546. $beg_sin = 0;
  547. $sec_sin = 0;
  548. $count = substr_count ( $p_sql, $strip_beg);
  549. $increment = 1;
  550. if ($strip_beg != $strip_end)
  551. $increment = 2;
  552. $i=0;
  553. $offset = 0;
  554. $strip_array = array();
  555. while ($i<$count) {
  556. $beg_sin = strpos($p_sql, $strip_beg, $offset);
  557. if (!$beg_sin)
  558. break;
  559. $sec_sin = strpos($p_sql, $strip_end, $beg_sin+1);
  560. $strip_array[$patt.$i] = substr($p_sql, $beg_sin, $sec_sin - $beg_sin +1);
  561. if ($increment > 1) {
  562. //we are in here because beginning and end patterns are not identical, so search for nesting
  563. $exists = strpos($strip_array[$patt.$i], $strip_beg );
  564. if ($exists>=0) {
  565. $nested_pos = (strrpos($strip_array[$patt.$i], $strip_beg ));
  566. $strip_array[$patt.$i] = substr($p_sql,$nested_pos+$beg_sin,$sec_sin - ($nested_pos+$beg_sin)+1);
  567. $p_sql = substr($p_sql, 0, $nested_pos+$beg_sin) . " ##". $patt.$i."## " . substr($p_sql, $sec_sin+1);
  568. $i = $i + 1;
  569. $beg_sin = $nested_pos;
  570. continue;
  571. }
  572. }
  573. $p_sql = substr($p_sql, 0, $beg_sin) . " ##". $patt.$i."## " . substr($p_sql, $sec_sin+1);
  574. //move the marker up
  575. $offset = $sec_sin+1;
  576. $i = $i + 1;
  577. }
  578. $strip_array['sql_string'] = $p_sql;
  579. return $strip_array;
  580. }
  581. /**
  582. * adds a pattern
  583. *
  584. * @param string $token
  585. * @param array $pattern_array
  586. * @return string
  587. */
  588. private function addPatternToSQL(
  589. $token,
  590. array $pattern_array
  591. )
  592. {
  593. //strip all single quotes out
  594. $pattern_array = array_reverse($pattern_array);
  595. foreach ($pattern_array as $key => $replace) {
  596. $token = str_replace( "##".$key."##", $replace,$token);
  597. }
  598. return $token;
  599. }
  600. /**
  601. * gets an alias from the sql statement
  602. *
  603. * @param string $sql
  604. * @param string $alias
  605. * @return string
  606. */
  607. private function getAliasFromSQL(
  608. $sql,
  609. $alias
  610. )
  611. {
  612. $matches = array();
  613. preg_match('/^(.*SELECT)(.*?FROM.*WHERE)(.*)$/isU',$sql, $matches);
  614. //parse all single and double quotes out of array
  615. $sin_array = $this->removePatternFromSQL($matches[2], "'", "'","sin_");
  616. $new_sql = array_pop($sin_array);
  617. $dub_array = $this->removePatternFromSQL($new_sql, "\"", "\"","dub_");
  618. $new_sql = array_pop($dub_array);
  619. //search for parenthesis
  620. $paren_array = $this->removePatternFromSQL($new_sql, "(", ")", "par_");
  621. $new_sql = array_pop($paren_array);
  622. //all functions should be removed now, so split the array on comma's
  623. $mstr_sql_array = explode(",", $new_sql);
  624. foreach($mstr_sql_array as $token ) {
  625. if (strpos($token, $alias)) {
  626. //found token, add back comments
  627. $token = $this->addPatternToSQL($token, $paren_array);
  628. $token = $this->addPatternToSQL($token, $dub_array);
  629. $token = $this->addPatternToSQL($token, $sin_array);
  630. //log and break out of this function
  631. return $token;
  632. }
  633. }
  634. return null;
  635. }
  636. /**
  637. * Finds the alias of the order by column, and then return the preceding column name
  638. *
  639. * @param string $sql
  640. * @param string $orderMatch
  641. * @return string
  642. */
  643. private function findColumnByAlias(
  644. $sql,
  645. $orderMatch
  646. )
  647. {
  648. //change case to lowercase
  649. $sql = strtolower($sql);
  650. $patt = '/\s+'.trim($orderMatch).'\s*,/';
  651. //check for the alias, it should contain comma, may contain space, \n, or \t
  652. $matches = array();
  653. preg_match($patt, $sql, $matches, PREG_OFFSET_CAPTURE);
  654. $found_in_sql = isset($matches[0][1]) ? $matches[0][1] : false;
  655. //set default for found variable
  656. $found = $found_in_sql;
  657. //if still no match found, then we need to parse through the string
  658. if (!$found_in_sql){
  659. //get count of how many times the match exists in string
  660. $found_count = substr_count($sql, $orderMatch);
  661. $i = 0;
  662. $first_ = 0;
  663. $len = strlen($orderMatch);
  664. //loop through string as many times as there is a match
  665. while ($found_count > $i) {
  666. //get the first match
  667. $found_in_sql = strpos($sql, $orderMatch,$first_);
  668. //make sure there was a match
  669. if($found_in_sql){
  670. //grab the next 2 individual characters
  671. $str_plusone = substr($sql,$found_in_sql + $len,1);
  672. $str_plustwo = substr($sql,$found_in_sql + $len+1,1);
  673. //if one of those characters is a comma, then we have our alias
  674. if ($str_plusone === "," || $str_plustwo === ","){
  675. //keep track of this position
  676. $found = $found_in_sql;
  677. }
  678. }
  679. //set the offset and increase the iteration counter
  680. $first_ = $found_in_sql+$len;
  681. $i = $i+1;
  682. }
  683. }
  684. //return $found, defaults have been set, so if no match was found it will be a negative number
  685. return $found;
  686. }
  687. /**
  688. * Return the order by string to use in case the column has been aliased
  689. *
  690. * @param string $sql
  691. * @param string $orig_order_match
  692. * @return string
  693. */
  694. private function returnOrderBy(
  695. $sql,
  696. $orig_order_match
  697. )
  698. {
  699. $sql = strtolower($sql);
  700. $orig_order_match = trim($orig_order_match);
  701. if (strpos($orig_order_match, "."))
  702. //this has a tablename defined, pass in the order match
  703. return $orig_order_match;
  704. //grab first space in order by
  705. $firstSpace = strpos($orig_order_match, " ");
  706. //split order by into column name and ascending/descending
  707. $orderMatch = " " . strtolower(substr($orig_order_match, 0, $firstSpace));
  708. $asc_desc = substr($orig_order_match,$firstSpace);
  709. //look for column name as an alias in sql string
  710. $found_in_sql = $this->findColumnByAlias($sql, $orderMatch);
  711. if (!$found_in_sql) {
  712. //check if this column needs the tablename prefixed to it
  713. $orderMatch = ".".trim($orderMatch);
  714. $colMatchPos = strpos($sql, $orderMatch);
  715. if ($colMatchPos !== false) {
  716. //grab sub string up to column name
  717. $containsColStr = substr($sql,0, $colMatchPos);
  718. //get position of first space, so we can grab table name
  719. $lastSpacePos = strrpos($containsColStr, " ");
  720. //use positions of column name, space before name, and length of column to find the correct column name
  721. $col_name = substr($sql, $lastSpacePos, $colMatchPos-$lastSpacePos+strlen($orderMatch));
  722. //bug 25485. When sorting by a custom field in Account List and then pressing NEXT >, system gives an error
  723. $containsCommaPos = strpos($col_name, ",");
  724. if($containsCommaPos !== false) {
  725. $col_name = substr($col_name, $containsCommaPos+1);
  726. }
  727. //return column name
  728. return $col_name;
  729. }
  730. //break out of here, log this
  731. $GLOBALS['log']->debug("No match was found for order by, pass string back untouched as: $orig_order_match");
  732. return $orig_order_match;
  733. }
  734. else {
  735. //if found, then parse and return
  736. //grab string up to the aliased column
  737. $GLOBALS['log']->debug("order by found, process sql string");
  738. $psql = (trim($this->getAliasFromSQL($sql, $orderMatch )));
  739. if (empty($psql))
  740. $psql = trim(substr($sql, 0, $found_in_sql));
  741. //grab the last comma before the alias
  742. $comma_pos = strrpos($psql, " ");
  743. //substring between the comma and the alias to find the joined_table alias and column name
  744. $col_name = substr($psql,0, $comma_pos);
  745. //make sure the string does not have an end parenthesis
  746. //and is not part of a function (i.e. "ISNULL(leads.last_name,'') as name" )
  747. //this is especially true for unified search from home screen
  748. $alias_beg_pos = 0;
  749. if(strpos($psql, " as "))
  750. $alias_beg_pos = strpos($psql, " as ");
  751. else if (strncasecmp($psql, 'isnull', 6))
  752. $alias_beg_pos = strpos($psql, " ");
  753. if ($alias_beg_pos > 0) {
  754. $col_name = substr($psql,0, $alias_beg_pos );
  755. }
  756. //add the "asc/desc" order back
  757. $col_name = $col_name. " ". $asc_desc;
  758. //pass in new order by
  759. $GLOBALS['log']->debug("order by being returned is " . $col_name);
  760. return $col_name;
  761. }
  762. }
  763. /**
  764. * Take in a string of the module and retrieve the correspondent table name
  765. *
  766. * @param string $module_str module name
  767. * @param string $sql SQL statement
  768. * @return string table name
  769. */
  770. private function getTableNameFromModuleName(
  771. $module_str,
  772. $sql
  773. )
  774. {
  775. global $beanList, $beanFiles;
  776. $GLOBALS['log']->debug("Module being processed is " . $module_str);
  777. //get the right module files
  778. //the module string exists in bean list, then process bean for correct table name
  779. //note that we exempt the reports module from this, as queries from reporting module should be parsed for
  780. //correct table name.
  781. if (($module_str != 'Reports' && $module_str != 'SavedReport') && isset($beanList[$module_str]) && isset($beanFiles[$beanList[$module_str]])){
  782. //if the class is not already loaded, then load files
  783. if (!class_exists($beanList[$module_str]))
  784. require_once($beanFiles[$beanList[$module_str]]);
  785. //instantiate new bean
  786. $module_bean = new $beanList[$module_str]();
  787. //get table name from bean
  788. $tbl_name = $module_bean->table_name;
  789. //make sure table name is not just a blank space, or empty
  790. $tbl_name = trim($tbl_name);
  791. if(empty($tbl_name)){
  792. $GLOBALS['log']->debug("Could not find table name for module $module_str. ");
  793. $tbl_name = $module_str;
  794. }
  795. }
  796. else {
  797. //since the module does NOT exist in beanlist, then we have to parse the string
  798. //and grab the table name from the passed in sql
  799. $GLOBALS['log']->debug("Could not find table name from module in request, retrieve from passed in sql");
  800. $tbl_name = $module_str;
  801. $sql = strtolower($sql);
  802. //look for the location of the "from" in sql string
  803. $fromLoc = strpos ( $sql,"from" );
  804. if ($fromLoc>0){
  805. //found from, substring from the "FROM" string in sql to end
  806. $tableEnd = substr($sql, $fromLoc+5);
  807. //We know that tablename will be next parameter after from, so
  808. //grab the next space after table name.
  809. // MFH BUG #14009: Also check to see if there are any carriage returns before the next space so that we don't grab any arbitrary joins or other tables.
  810. $carriage_ret = strpos($tableEnd,"\n");
  811. $next_space = strpos ( $tableEnd," " );
  812. if ($carriage_ret < $next_space)
  813. $next_space = $carriage_ret;
  814. if ($next_space > 0) {
  815. $tbl_name= substr($tableEnd,0, $next_space);
  816. if(empty($tbl_name)){
  817. $GLOBALS['log']->debug("Could not find table name sql either, return $module_str. ");
  818. $tbl_name = $module_str;
  819. }
  820. }
  821. //grab the table, to see if it is aliased
  822. $aliasTableEnd = trim(substr($tableEnd, $next_space));
  823. $alias_space = strpos ($aliasTableEnd, " " );
  824. if ($alias_space > 0){
  825. $alias_tbl_name= substr($aliasTableEnd,0, $alias_space);
  826. strtolower($alias_tbl_name);
  827. if(empty($alias_tbl_name)
  828. || $alias_tbl_name == "where"
  829. || $alias_tbl_name == "inner"
  830. || $alias_tbl_name == "left"
  831. || $alias_tbl_name == "join"
  832. || $alias_tbl_name == "outer"
  833. || $alias_tbl_name == "right") {
  834. //not aliased, do nothing
  835. }
  836. elseif ($alias_tbl_name == "as") {
  837. //the next word is the table name
  838. $aliasTableEnd = trim(substr($aliasTableEnd, $alias_space));
  839. $alias_space = strpos ($aliasTableEnd, " " );
  840. if ($alias_space > 0) {
  841. $alias_tbl_name= trim(substr($aliasTableEnd,0, $alias_space));
  842. if (!empty($alias_tbl_name))
  843. $tbl_name = $alias_tbl_name;
  844. }
  845. }
  846. else {
  847. //this is table alias
  848. $tbl_name = $alias_tbl_name;
  849. }
  850. }
  851. }
  852. }
  853. //return table name
  854. $GLOBALS['log']->debug("Table name for module $module_str is: ".$tbl_name);
  855. return $tbl_name;
  856. }
  857. /**
  858. * @see DBManager::getFieldsArray()
  859. */
  860. public function getFieldsArray(
  861. &$result,
  862. $make_lower_case = false
  863. )
  864. {
  865. $field_array = array();
  866. if(! isset($result) || empty($result))
  867. return 0;
  868. $i = 0;
  869. while ($i < mssql_num_fields($result)) {
  870. $meta = mssql_fetch_field($result, $i);
  871. if (!$meta)
  872. return 0;
  873. if($make_lower_case==true)
  874. $meta->name = strtolower($meta->name);
  875. $field_array[] = $meta->name;
  876. $i++;
  877. }
  878. return $field_array;
  879. }
  880. /**
  881. * @see DBManager::getAffectedRowCount()
  882. */
  883. public function getAffectedRowCount()
  884. {
  885. return $this->getOne("SELECT @@ROWCOUNT");
  886. }
  887. /**
  888. * @see DBManager::describeField()
  889. */
  890. protected function describeField(
  891. $name,
  892. $tablename
  893. )
  894. {
  895. global $table_descriptions;
  896. if(isset($table_descriptions[$tablename]) && isset($table_descriptions[$tablename][$name])){
  897. return $table_descriptions[$tablename][$name];
  898. }
  899. $table_descriptions[$tablename] = array();
  900. $sql = sprintf( "SELECT COLUMN_NAME AS Field
  901. , DATA_TYPE + CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL
  902. THEN '(' + RTRIM(CAST(CHARACTER_MAXIMUM_LENGTH AS CHAR)) + ')'
  903. ELSE '' END as 'Type'
  904. , CHARACTER_MAXIMUM_LENGTH
  905. , IS_NULLABLE AS 'Null'
  906. , CASE WHEN COLUMN_DEFAULT LIKE '((0))' THEN '(''0'')' ELSE COLUMN_DEFAULT END as 'Default'
  907. FROM INFORMATION_SCHEMA.COLUMNS
  908. WHERE TABLE_NAME = '%s'",
  909. $tablename
  910. );
  911. $result = $this->query($sql);
  912. while ($row = $this->fetchByAssoc($result) )
  913. $table_descriptions[$tablename][$row['Field']] = $row;
  914. if (isset($table_descriptions[$tablename][$name]))
  915. return $table_descriptions[$tablename][$name];
  916. return array();
  917. }
  918. /**
  919. * @see DBManager::fetchByAssoc()
  920. */
  921. public function fetchByAssoc(
  922. &$result,
  923. $rowNum = -1,
  924. $encode = true
  925. )
  926. {
  927. if (!$result)
  928. return false;
  929. if ($result && $rowNum < 0) {
  930. $row = mssql_fetch_assoc($result);
  931. //MSSQL returns a space " " when a varchar column is empty ("") and not null.
  932. //We need to iterate through the returned row array and strip empty spaces
  933. if(!empty($row)){
  934. foreach($row as $key => $column) {
  935. //notice we only strip if one space is returned. we do not want to strip
  936. //strings with intentional spaces (" foo ")
  937. if (!empty($column) && $column ==" ") {
  938. $row[$key] = '';
  939. }
  940. }
  941. }
  942. if($encode && $this->encode&& is_array($row))
  943. return array_map('to_html', $row);
  944. return $row;
  945. }
  946. if ($this->getRowCount($result) > $rowNum) {
  947. if ( $rowNum == -1 )
  948. $rowNum = 0;
  949. @mssql_data_seek($result, $rowNum);
  950. }
  951. $this->lastmysqlrow = $rowNum;
  952. $row = @mssql_fetch_assoc($result);
  953. if($encode && $this->encode && is_array($row))
  954. return array_map('to_html', $row);
  955. return $row;
  956. }
  957. /**
  958. * @see DBManager::quote()
  959. */
  960. public function quote(
  961. $string,
  962. $isLike = true
  963. )
  964. {
  965. return $string = str_replace("'","''", parent::quote($string));
  966. }
  967. /**
  968. * @see DBManager::quoteForEmail()
  969. */
  970. public function quoteForEmail(
  971. $string,
  972. $isLike = true
  973. )
  974. {
  975. return str_replace("'","''", $string);
  976. }
  977. /**
  978. * @see DBManager::tableExists()
  979. */
  980. public function tableExists(
  981. $tableName
  982. )
  983. {
  984. $GLOBALS['log']->info("tableExists: $tableName");
  985. $this->checkConnection();
  986. $result = $this->query(
  987. "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND TABLE_NAME='".$tableName."'");
  988. $rowCount = $this->getRowCount($result);
  989. $this->freeResult($result);
  990. return ($rowCount == 0) ? false : true;
  991. }
  992. /**
  993. * @see DBManager::addIndexes()
  994. */
  995. public function addIndexes(
  996. $tablename,
  997. $indexes,
  998. $execute = true
  999. )
  1000. {
  1001. $alters = $this->helper->indexSQL($tablename,array(),$indexes);
  1002. if ($execute)
  1003. $this->query($alters);
  1004. return $alters;
  1005. }
  1006. /**
  1007. * @see DBManager::dropIndexes()
  1008. */
  1009. public function dropIndexes(
  1010. $tablename,
  1011. $indexes,
  1012. $execute = true
  1013. )
  1014. {
  1015. $sql = '';
  1016. foreach ($indexes as $index) {
  1017. if ( !empty($sql) ) $sql .= ";";
  1018. $name = $index['name'];
  1019. if($execute)
  1020. unset($GLOBALS['table_descriptions'][$tablename]['indexes'][$name]);
  1021. if ($index['type'] == 'primary')
  1022. $sql .= "ALTER TABLE $tablename DROP CONSTRAINT $name";
  1023. else
  1024. $sql .= "DROP INDEX $name on $tablename";
  1025. }
  1026. if (!empty($sql))
  1027. if($execute)
  1028. $this->query($sql);
  1029. return $sql;
  1030. }
  1031. /**
  1032. * @see DBManager::checkQuery()
  1033. */
  1034. protected function checkQuery(
  1035. $sql
  1036. )
  1037. {
  1038. return true;
  1039. }
  1040. /**
  1041. * @see DBManager::getTablesArray()
  1042. */
  1043. public function getTablesArray()
  1044. {
  1045. $GLOBALS['log']->debug('MSSQL fetching table list');
  1046. if($this->getDatabase()) {
  1047. $tables = array();
  1048. $r = $this->query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES');
  1049. if (is_resource($r)) {
  1050. while ($a = $this->fetchByAssoc($r))
  1051. $tables[] = $a['TABLE_NAME'];
  1052. return $tables;
  1053. }
  1054. }
  1055. return false; // no database available
  1056. }
  1057. /**
  1058. * This call is meant to be used during install, when Full Text Search is enabled
  1059. * Indexing would always occur after a fresh sql server install, so this code creates
  1060. * a catalog and table with full text index.
  1061. */
  1062. public function wakeupFTS()
  1063. {
  1064. $GLOBALS['log']->debug('MSSQL about to wakeup FTS');
  1065. if($this->getDatabase()) {
  1066. //create wakup catalog
  1067. $FTSqry[] = "if not exists( select * from sys.fulltext_catalogs where name ='wakeup_catalog' )
  1068. CREATE FULLTEXT CATALOG wakeup_catalog
  1069. ";
  1070. //drop wakeup table if it exists
  1071. $FTSqry[] = "IF EXISTS(SELECT 'fts_wakeup' FROM sysobjects WHERE name = 'fts_wakeup' AND xtype='U')
  1072. DROP TABLE fts_wakeup
  1073. ";
  1074. //create wakeup table
  1075. $FTSqry[] = "CREATE TABLE fts_wakeup(
  1076. id varchar(36) NOT NULL CONSTRAINT pk_fts_wakeup_id PRIMARY KEY CLUSTERED (id ASC ),
  1077. body text NULL,
  1078. kb_index int IDENTITY(1,1) NOT NULL CONSTRAINT wakeup_fts_unique_idx UNIQUE NONCLUSTERED
  1079. )
  1080. ";
  1081. //create full text index
  1082. $FTSqry[] = "CREATE FULLTEXT INDEX ON fts_wakeup
  1083. (
  1084. body
  1085. Language 0X0
  1086. )
  1087. KEY INDEX wakeup_fts_unique_idx ON wakeup_catalog
  1088. WITH CHANGE_TRACKING AUTO
  1089. ";
  1090. //insert dummy data
  1091. $FTSqry[] = "INSERT INTO fts_wakeup (id ,body)
  1092. VALUES ('".create_guid()."', 'SugarCRM Rocks' )";
  1093. //create queries to stop and restart indexing
  1094. $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup STOP POPULATION';
  1095. $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup DISABLE';
  1096. $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup ENABLE';
  1097. $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup SET CHANGE_TRACKING MANUAL';
  1098. $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup START FULL POPULATION';
  1099. $FTSqry[] = 'ALTER FULLTEXT INDEX ON fts_wakeup SET CHANGE_TRACKING AUTO';
  1100. foreach($FTSqry as $q){
  1101. sleep(3);
  1102. $this->query($q);
  1103. }
  1104. }
  1105. return false; // no database available
  1106. }
  1107. /**
  1108. * @see DBManager::convert()
  1109. */
  1110. public function convert(
  1111. $string,
  1112. $type,
  1113. array $additional_parameters = array(),
  1114. array $additional_parameters_oracle_only = array()
  1115. )
  1116. {
  1117. // convert the parameters array into a comma delimited string
  1118. $additional_parameters_string = '';
  1119. if (!empty($additional_parameters))
  1120. $additional_parameters_string = ','.implode(',',$additional_parameters);
  1121. switch ($type) {
  1122. case 'today': return "GETDATE()";
  1123. case 'left': return "LEFT($string".$additional_parameters_string.")";
  1124. case 'date_format':
  1125. if(!empty($additional_parameters) && in_array("'%Y-%m'", $additional_parameters))
  1126. return "CONVERT(varchar(7),". $string . ",120)";
  1127. else
  1128. return "CONVERT(varchar(10),". $string . ",120)";
  1129. case 'IFNULL': return "ISNULL($string".$additional_parameters_string.")";
  1130. case 'CONCAT': return "$string+".implode("+",$additional_parameters);
  1131. case 'text2char': return "CAST($string AS varchar(8000))";
  1132. }
  1133. return "$string";
  1134. }
  1135. /**
  1136. * @see DBManager::concat()
  1137. */
  1138. public function concat(
  1139. $table,
  1140. array $fields

Large files files are truncated, but you can click here to view the full file