PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/include/database/SqlsrvManager.php

https://github.com/mikmagic/sugarcrm_dev
PHP | 459 lines | 253 code | 47 blank | 159 comment | 50 complexity | 659363159024ac1885d50f8a8e3c31e3 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-3-Clause, AGPL-3.0
  1. <?php
  2. if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
  3. /*********************************************************************************
  4. * SugarCRM Community Edition is a customer relationship management program developed by
  5. * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
  6. *
  7. * This program is free software; you can redistribute it and/or modify it under
  8. * the terms of the GNU Affero General Public License version 3 as published by the
  9. * Free Software Foundation with the addition of the following permission added
  10. * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
  11. * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
  12. * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
  13. *
  14. * This program is distributed in the hope that it will be useful, but WITHOUT
  15. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  17. * details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License along with
  20. * this program; if not, see http://www.gnu.org/licenses or write to the Free
  21. * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  22. * 02110-1301 USA.
  23. *
  24. * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
  25. * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
  26. *
  27. * The interactive user interfaces in modified source and object code versions
  28. * of this program must display Appropriate Legal Notices, as required under
  29. * Section 5 of the GNU Affero General Public License version 3.
  30. *
  31. * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
  32. * these Appropriate Legal Notices must retain the display of the "Powered by
  33. * SugarCRM" logo. If the display of the logo is not reasonably feasible for
  34. * technical reasons, the Appropriate Legal Notices must display the words
  35. * "Powered by SugarCRM".
  36. ********************************************************************************/
  37. /*********************************************************************************
  38. * 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. include_once('include/database/MssqlManager.php');
  89. class SqlsrvManager extends MssqlManager
  90. {
  91. /**
  92. * @see DBManager::$backendFunctions
  93. */
  94. protected $backendFunctions = array(
  95. 'free_result' => 'sqlsrv_free_stmt',
  96. 'close' => 'sqlsrv_close',
  97. );
  98. /**
  99. * cache of the results sets as they are fetched
  100. */
  101. protected $_resultsCache;
  102. /**
  103. * cache of the results sets as they are fetched
  104. */
  105. protected $_lastResultsCacheKey = 0;
  106. public function __construct()
  107. {
  108. parent::__construct();
  109. $this->_resultsCache = new ArrayObject;
  110. }
  111. /**
  112. * @see DBManager::connect()
  113. */
  114. public function connect(
  115. array $configOptions = null,
  116. $dieOnError = false
  117. )
  118. {
  119. global $sugar_config;
  120. if (is_null($configOptions))
  121. $configOptions = $sugar_config['dbconfig'];
  122. //set the connections parameters
  123. $connect_param = '';
  124. $configOptions['db_host_instance'] = trim($configOptions['db_host_instance']);
  125. if (empty($configOptions['db_host_instance']))
  126. $connect_param = $configOptions['db_host_name'];
  127. else
  128. $connect_param = $configOptions['db_host_name']."\\".$configOptions['db_host_instance'];
  129. /*
  130. * Don't try to specifically use a persistent connection
  131. * since the driver will handle that for us
  132. */
  133. $this->database = sqlsrv_connect(
  134. $connect_param ,
  135. array(
  136. "UID" => $configOptions['db_user_name'],
  137. "PWD" => $configOptions['db_password'],
  138. "Database" => $configOptions['db_name'],
  139. "CharacterSet" => "UTF-8",
  140. "ReturnDatesAsStrings" => true,
  141. "MultipleActiveResultSets" => true,
  142. )
  143. );
  144. if(empty($this->database)) {
  145. $GLOBALS['log']->fatal("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].".");
  146. sugar_die($GLOBALS['app_strings']['ERR_NO_DB']);
  147. }
  148. if($this->checkError('Could Not Connect:', $dieOnError))
  149. $GLOBALS['log']->info("connected to db");
  150. sqlsrv_query($this->database, 'SET DATEFORMAT mdy');
  151. $GLOBALS['log']->info("Connect:".$this->database);
  152. }
  153. /**
  154. * @see DBManager::checkError()
  155. */
  156. public function checkError(
  157. $msg = '',
  158. $dieOnError = false
  159. )
  160. {
  161. if (DBManager::checkError($msg, $dieOnError))
  162. return true;
  163. $sqlmsg = $this->_getLastErrorMessages();
  164. $sqlpos = strpos($sqlmsg, 'Changed database context to');
  165. $sqlpos2 = strpos($sqlmsg, 'Warning:');
  166. $sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
  167. if ( $sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false )
  168. $sqlmsg = ''; // empty out sqlmsg if its something we will ignor
  169. else {
  170. global $app_strings;
  171. //ERR_MSSQL_DB_CONTEXT: localized version of 'Changed database context to' message
  172. if (empty($app_strings)
  173. or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])
  174. or !isset($app_strings['ERR_MSSQL_WARNING']) ) {
  175. //ignore the message from sql-server if $app_strings array is empty. This will happen
  176. //only if connection if made before languge is set.
  177. $GLOBALS['log']->debug("Ignoring this database message: " . $sqlmsg);
  178. $sqlmsg = '';
  179. }
  180. else {
  181. $sqlpos = strpos($sqlmsg, $app_strings['ERR_MSSQL_DB_CONTEXT']);
  182. $sqlpos2 = strpos($sqlmsg, $app_strings['ERR_MSSQL_WARNING']);
  183. if ( $sqlpos !== false || $sqlpos2 !== false)
  184. $sqlmsg = '';
  185. }
  186. }
  187. if ( strlen($sqlmsg) > 2 ) {
  188. $GLOBALS['log']->fatal("SQL Server error: " . $sqlmsg);
  189. return true;
  190. }
  191. return false;
  192. }
  193. /**
  194. * @see DBManager::query()
  195. */
  196. public function query(
  197. $sql,
  198. $dieOnError = false,
  199. $msg = '',
  200. $suppress = false
  201. )
  202. {
  203. global $app_strings;
  204. $sql = $this->_appendN($sql);
  205. $this->countQuery($sql);
  206. $GLOBALS['log']->info('Query:' . $sql);
  207. $this->checkConnection();
  208. $this->query_time = microtime(true);
  209. if ($suppress) {
  210. }
  211. else {
  212. $result = @sqlsrv_query($this->database, $sql);
  213. }
  214. if (!$result) {
  215. // awu Bug 10657: ignoring mssql error message 'Changed database context to' - an intermittent
  216. // and difficult to reproduce error. The message is only a warning, and does
  217. // not affect the functionality of the query
  218. $sqlmsg = $this->_getLastErrorMessages();
  219. $sqlpos = strpos($sqlmsg, 'Changed database context to');
  220. $sqlpos2 = strpos($sqlmsg, 'Warning:');
  221. $sqlpos3 = strpos($sqlmsg, 'Checking identity information:');
  222. if ($sqlpos !== false || $sqlpos2 !== false || $sqlpos3 !== false) // if sqlmsg has 'Changed database context to', just log it
  223. $GLOBALS['log']->debug($sqlmsg . ": " . $sql );
  224. else {
  225. $GLOBALS['log']->fatal($sqlmsg . ": " . $sql );
  226. if($dieOnError)
  227. sugar_die('SQL Error : ' . $sqlmsg);
  228. else
  229. echo 'SQL Error : ' . $sqlmsg;
  230. }
  231. }
  232. $this->lastmysqlrow = -1;
  233. $this->query_time = microtime(true) - $this->query_time;
  234. $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
  235. $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
  236. return $result;
  237. }
  238. /**
  239. * @see DBManager::getFieldsArray()
  240. */
  241. public function getFieldsArray(
  242. &$result,
  243. $make_lower_case = false
  244. )
  245. {
  246. $field_array = array();
  247. if ( !$result ) {
  248. return false;
  249. }
  250. foreach ( sqlsrv_field_metadata($result) as $fieldMetadata ) {
  251. $key = $fieldMetadata['Name'];
  252. if($make_lower_case==true)
  253. $key = strtolower($key);
  254. $field_array[] = $key;
  255. }
  256. return $field_array;
  257. }
  258. /**
  259. * @see DBManager::fetchByAssoc()
  260. */
  261. public function fetchByAssoc(
  262. &$result,
  263. $rowNum = -1,
  264. $encode = true
  265. )
  266. {
  267. if (!$result) {
  268. return false;
  269. }
  270. $row = sqlsrv_fetch_array($result,SQLSRV_FETCH_ASSOC);
  271. if (empty($row)) {
  272. return false;
  273. }
  274. foreach($row as $key => $column) {
  275. // MSSQL returns a space " " when a varchar column is empty ("") and not null.
  276. // We need to strip empty spaces
  277. // notice we only strip if one space is returned. we do not want to strip
  278. // strings with intentional spaces (" foo ")
  279. if (!empty($column) && $column ==" ") {
  280. $row[$key] = '';
  281. }
  282. // Strip off the extra .000 off of datetime fields
  283. $matches = array();
  284. preg_match('/^([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}).[0-9]{3}$/',$column,$matches);
  285. if ( !empty($matches) && !empty($matches[1]) ) {
  286. $row[$key] = $matches[1];
  287. }
  288. // HTML encode if needed
  289. if($encode && $this->encode) {
  290. $row[$key] = to_html($row[$key]);
  291. }
  292. }
  293. return $row;
  294. }
  295. /**
  296. * @see DBManager::getRowCount()
  297. */
  298. public function getRowCount(
  299. &$result
  300. )
  301. {
  302. return $this->getOne('SELECT @@ROWCOUNT');
  303. }
  304. /**
  305. * Emulates old mssql_get_last_message() behavior, giving us any error messages from the previous
  306. * function call
  307. *
  308. * @return string error message(s)
  309. */
  310. private function _getLastErrorMessages()
  311. {
  312. $message = '';
  313. if ( ($errors = sqlsrv_errors()) != null)
  314. foreach ( $errors as $error )
  315. $message .= $error['message'] . '. ';
  316. return $message;
  317. }
  318. /**
  319. * @see DBManager::convert()
  320. */
  321. public function convert(
  322. $string,
  323. $type,
  324. array $additional_parameters = array(),
  325. array $additional_parameters_oracle_only = array()
  326. )
  327. {
  328. if ( $type == 'datetime')
  329. return "CONVERT(varchar(25)," . $string . ",120)";
  330. else
  331. return parent::convert($string, $type, $additional_parameters, $additional_parameters_oracle_only);
  332. }
  333. /**
  334. * This is a utility function to prepend the "N" character in front of SQL values that are
  335. * surrounded by single quotes.
  336. *
  337. * @param $sql string SQL statement
  338. * @return string SQL statement with single quote values prepended with "N" character for nvarchar columns
  339. */
  340. private function _appendN(
  341. $sql
  342. )
  343. {
  344. // If there are no single quotes, don't bother, will just assume there is no character data
  345. if (strpos($sql, "'") === false)
  346. return $sql;
  347. // Flag if there are odd number of single quotes, just continue w/o trying to append N
  348. if ((substr_count($sql, "'") & 1)) {
  349. $GLOBALS['log']->error("SQL statement[" . $sql . "] has odd number of single quotes.");
  350. return $sql;
  351. }
  352. //The only location of three subsequent ' will be at the begning or end of a value.
  353. $sql = preg_replace('/(?<!\')(\'{3})(?!\')/', "'<@#@#@PAIR@#@#@>", $sql);
  354. // Remove any remaining '' and do not parse... replace later (hopefully we don't even have any)
  355. $pairs = array();
  356. $regexp = '/(\'{2})/';
  357. $pair_matches = array();
  358. preg_match_all($regexp, $sql, $pair_matches);
  359. if ($pair_matches) {
  360. foreach (array_unique($pair_matches[0]) as $key=>$value) {
  361. $pairs['<@PAIR-'.$key.'@>'] = $value;
  362. }
  363. if (!empty($pairs)) {
  364. $sql = str_replace($pairs, array_keys($pairs), $sql);
  365. }
  366. }
  367. $regexp = "/(N?\'.+?\')/is";
  368. $matches = array();
  369. preg_match_all($regexp, $sql, $matches);
  370. $replace = array();
  371. if (!empty($matches)) {
  372. foreach ($matches[0] as $key=>$value) {
  373. // We are assuming that all nvarchar columns are no more than 200 characters in length
  374. // One problem we face is the image column type in reports which cannot accept nvarchar data
  375. if (!empty($value) && !is_numeric(trim(str_replace(array("'", ","), "", $value))) && !preg_match('/^\'[\,]\'$/', $value)) {
  376. $replace[$value] = 'N' . trim($value, "N");
  377. }
  378. }
  379. }
  380. if (!empty($replace))
  381. $sql = str_replace(array_keys($replace), $replace, $sql);
  382. if (!empty($pairs))
  383. $sql = str_replace(array_keys($pairs), $pairs, $sql);
  384. if(strpos($sql, "<@#@#@PAIR@#@#@>"))
  385. $sql = str_replace(array('<@#@#@PAIR@#@#@>'), array("''"), $sql);
  386. return $sql;
  387. }
  388. /**
  389. * Compares two vardefs. Overriding 39098 due to bug: 39098 . IN 6.0 we changed the id columns to dbType = 'id'
  390. * for example emails_beans. In 554 the field email_id was nvarchar but in 6.0 since it id dbType = 'id' we would want to alter
  391. * it to varchar. This code will prevent it.
  392. *
  393. * @param array $fielddef1
  394. * @param array $fielddef2
  395. * @return bool true if they match, false if they don't
  396. */
  397. public function compareVarDefs($fielddef1,$fielddef2)
  398. {
  399. if((isset($fielddef2['dbType']) && $fielddef2['dbType'] == 'id') || preg_match('/(_id$|^id$)/', $fielddef2['name'])){
  400. if(isset($fielddef1['type']) && isset($fielddef2['type'])){
  401. $fielddef2['type'] = $fielddef1['type'];
  402. }
  403. }
  404. return parent::compareVarDefs($fielddef1, $fielddef2);
  405. }
  406. }