PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/rokcommon/Doctrine/Connection/Mssql.php

https://bitbucket.org/pastor399/newcastleunifc
PHP | 407 lines | 208 code | 53 blank | 146 comment | 32 complexity | 5d21ccc7d02bea9aabdc3dbe123eed1b MD5 | raw file
  1. <?php
  2. /*
  3. * $Id: Mssql.php 48519 2012-02-03 23:18:52Z btowles $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information, see
  19. * <http://www.doctrine-project.org>.
  20. */
  21. /**
  22. * Doctrine_Connection_Mssql
  23. *
  24. * @package Doctrine
  25. * @subpackage Connection
  26. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  27. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
  28. * @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
  29. * @version $Revision: 7659 $
  30. * @link www.doctrine-project.org
  31. * @since 1.0
  32. */
  33. class Doctrine_Connection_Mssql extends Doctrine_Connection_Common
  34. {
  35. /**
  36. * @var string $driverName the name of this connection driver
  37. */
  38. protected $driverName = 'Mssql';
  39. /**
  40. * the constructor
  41. *
  42. * @param Doctrine_Manager $manager
  43. * @param PDO $pdo database handle
  44. */
  45. public function __construct(Doctrine_Manager $manager, $adapter)
  46. {
  47. // initialize all driver options
  48. $this->supported = array(
  49. 'sequences' => 'emulated',
  50. 'indexes' => true,
  51. 'affected_rows' => true,
  52. 'transactions' => true,
  53. 'summary_functions' => true,
  54. 'order_by_text' => true,
  55. 'current_id' => 'emulated',
  56. 'limit_queries' => 'emulated',
  57. 'LOBs' => true,
  58. 'replace' => 'emulated',
  59. 'sub_selects' => true,
  60. 'auto_increment' => true,
  61. 'primary_key' => true,
  62. 'result_introspection' => true,
  63. 'prepared_statements' => 'emulated',
  64. );
  65. $this->properties['varchar_max_length'] = 8000;
  66. parent::__construct($manager, $adapter);
  67. }
  68. /**
  69. * quoteIdentifier
  70. * Quote a string so it can be safely used as a table / column name
  71. *
  72. * Quoting style depends on which database driver is being used.
  73. *
  74. * @param string $identifier identifier name to be quoted
  75. * @param bool $checkOption check the 'quote_identifier' option
  76. *
  77. * @return string quoted identifier string
  78. */
  79. public function quoteIdentifier($identifier, $checkOption = false)
  80. {
  81. if ($checkOption && ! $this->getAttribute(Doctrine_Core::ATTR_QUOTE_IDENTIFIER)) {
  82. return $identifier;
  83. }
  84. if (strpos($identifier, '.') !== false) {
  85. $parts = explode('.', $identifier);
  86. $quotedParts = array();
  87. foreach ($parts as $p) {
  88. $quotedParts[] = $this->quoteIdentifier($p);
  89. }
  90. return implode('.', $quotedParts);
  91. }
  92. return '[' . str_replace(']', ']]', $identifier) . ']';
  93. }
  94. /**
  95. * Adds an adapter-specific LIMIT clause to the SELECT statement.
  96. * [ original code borrowed from Zend Framework ]
  97. *
  98. * License available at: http://framework.zend.com/license
  99. *
  100. * Copyright (c) 2005-2008, Zend Technologies USA, Inc.
  101. * All rights reserved.
  102. *
  103. * Redistribution and use in source and binary forms, with or without modification,
  104. * are permitted provided that the following conditions are met:
  105. *
  106. * * Redistributions of source code must retain the above copyright notice,
  107. * this list of conditions and the following disclaimer.
  108. *
  109. * * Redistributions in binary form must reproduce the above copyright notice,
  110. * this list of conditions and the following disclaimer in the documentation
  111. * and/or other materials provided with the distribution.
  112. *
  113. * * Neither the name of Zend Technologies USA, Inc. nor the names of its
  114. * contributors may be used to endorse or promote products derived from this
  115. * software without specific prior written permission.
  116. *
  117. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  118. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  119. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  120. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  121. * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  122. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  123. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  124. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  125. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  126. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  127. *
  128. * @param string $query
  129. * @param mixed $limit
  130. * @param mixed $offset
  131. * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
  132. * @return string
  133. */
  134. public function modifyLimitQuery($query, $limit = false, $offset = false, $isManip = false, $isSubQuery = false)
  135. {
  136. if ($limit > 0) {
  137. $count = intval($limit);
  138. $offset = intval($offset);
  139. if ($offset < 0) {
  140. throw new Doctrine_Connection_Exception("LIMIT argument offset=$offset is not valid");
  141. }
  142. $orderby = stristr($query, 'ORDER BY');
  143. if ($orderby !== false) {
  144. // Ticket #1835: Fix for ORDER BY alias
  145. // Ticket #2050: Fix for multiple ORDER BY clause
  146. $order = str_ireplace('ORDER BY', '', $orderby);
  147. $orders = explode(',', $order);
  148. for ($i = 0; $i < count($orders); $i++) {
  149. $sorts[$i] = (stripos($orders[$i], ' desc') !== false) ? 'DESC' : 'ASC';
  150. $orders[$i] = trim(preg_replace('/\s+(ASC|DESC)$/i', '', $orders[$i]));
  151. // find alias in query string
  152. $helper_string = stristr($query, $orders[$i]);
  153. $from_clause_pos = strpos($helper_string, ' FROM ');
  154. $fields_string = substr($helper_string, 0, $from_clause_pos + 1);
  155. $field_array = explode(',', $fields_string);
  156. $field_array = array_shift($field_array);
  157. $aux2 = spliti(' as ', $field_array);
  158. $aux2 = explode('.', end($aux2));
  159. $aliases[$i] = trim(end($aux2));
  160. }
  161. }
  162. // Ticket #1259: Fix for limit-subquery in MSSQL
  163. $selectRegExp = 'SELECT\s+';
  164. $selectReplace = 'SELECT ';
  165. if (preg_match('/^SELECT(\s+)DISTINCT/i', $query)) {
  166. $selectRegExp .= 'DISTINCT\s+';
  167. $selectReplace .= 'DISTINCT ';
  168. }
  169. $fields_string = substr($query, strlen($selectReplace), strpos($query, ' FROM ') - strlen($selectReplace));
  170. $field_array = explode(',', $fields_string);
  171. $field_array = array_shift($field_array);
  172. $aux2 = spliti(' as ', $field_array);
  173. $aux2 = explode('.', end($aux2));
  174. $key_field = trim(end($aux2));
  175. $query = preg_replace('/^'.$selectRegExp.'/i', $selectReplace . 'TOP ' . ($count + $offset) . ' ', $query);
  176. if ($isSubQuery === true) {
  177. $query = 'SELECT TOP ' . $count . ' ' . $this->quoteIdentifier('inner_tbl') . '.' . $key_field . ' FROM (' . $query . ') AS ' . $this->quoteIdentifier('inner_tbl');
  178. } else {
  179. $query = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $query . ') AS ' . $this->quoteIdentifier('inner_tbl');
  180. }
  181. if ($orderby !== false) {
  182. $query .= ' ORDER BY ';
  183. for ($i = 0, $l = count($orders); $i < $l; $i++) {
  184. if ($i > 0) { // not first order clause
  185. $query .= ', ';
  186. }
  187. $query .= $this->quoteIdentifier('inner_tbl') . '.' . $aliases[$i] . ' ';
  188. $query .= (stripos($sorts[$i], 'asc') !== false) ? 'DESC' : 'ASC';
  189. }
  190. }
  191. if ($isSubQuery !== true) {
  192. $query .= ') AS ' . $this->quoteIdentifier('outer_tbl');
  193. if ($orderby !== false) {
  194. $query .= ' ORDER BY ';
  195. for ($i = 0, $l = count($orders); $i < $l; $i++) {
  196. if ($i > 0) { // not first order clause
  197. $query .= ', ';
  198. }
  199. $query .= $this->quoteIdentifier('outer_tbl') . '.' . $aliases[$i] . ' ' . $sorts[$i];
  200. }
  201. }
  202. }
  203. }
  204. return $query;
  205. }
  206. /**
  207. * Creates dbms specific LIMIT/OFFSET SQL for the subqueries that are used in the
  208. * context of the limit-subquery algorithm.
  209. *
  210. * @return string
  211. */
  212. public function modifyLimitSubquery(Doctrine_Table $rootTable, $query, $limit = false, $offset = false, $isManip = false)
  213. {
  214. return $this->modifyLimitQuery($query, $limit, $offset, $isManip, true);
  215. }
  216. /**
  217. * return version information about the server
  218. *
  219. * @param bool $native determines if the raw version string should be returned
  220. * @return array version information
  221. */
  222. public function getServerVersion($native = false)
  223. {
  224. if ($this->serverInfo) {
  225. $serverInfo = $this->serverInfo;
  226. } else {
  227. $query = 'SELECT @@VERSION';
  228. $serverInfo = $this->fetchOne($query);
  229. }
  230. // cache server_info
  231. $this->serverInfo = $serverInfo;
  232. if ( ! $native) {
  233. if (preg_match('/([0-9]+)\.([0-9]+)\.([0-9]+)/', $serverInfo, $tmp)) {
  234. $serverInfo = array(
  235. 'major' => $tmp[1],
  236. 'minor' => $tmp[2],
  237. 'patch' => $tmp[3],
  238. 'extra' => null,
  239. 'native' => $serverInfo,
  240. );
  241. } else {
  242. $serverInfo = array(
  243. 'major' => null,
  244. 'minor' => null,
  245. 'patch' => null,
  246. 'extra' => null,
  247. 'native' => $serverInfo,
  248. );
  249. }
  250. }
  251. return $serverInfo;
  252. }
  253. /**
  254. * Checks if there's a sequence that exists.
  255. *
  256. * @param string $seq_name The sequence name to verify.
  257. * @return boolean The value if the table exists or not
  258. */
  259. public function checkSequence($seqName)
  260. {
  261. $query = 'SELECT * FROM ' . $seqName;
  262. try {
  263. $this->exec($query);
  264. } catch(Doctrine_Connection_Exception $e) {
  265. if ($e->getPortableCode() == Doctrine_Core::ERR_NOSUCHTABLE) {
  266. return false;
  267. }
  268. throw $e;
  269. }
  270. return true;
  271. }
  272. /**
  273. * execute
  274. * @param string $query sql query
  275. * @param array $params query parameters
  276. *
  277. * @return PDOStatement|Doctrine_Adapter_Statement
  278. */
  279. public function execute($query, array $params = array())
  280. {
  281. if(! empty($params)) {
  282. $query = $this->replaceBoundParamsWithInlineValuesInQuery($query, $params);
  283. }
  284. return parent::execute($query, array());
  285. }
  286. /**
  287. * execute
  288. * @param string $query sql query
  289. * @param array $params query parameters
  290. *
  291. * @return PDOStatement|Doctrine_Adapter_Statement
  292. */
  293. public function exec($query, array $params = array())
  294. {
  295. if(! empty($params)) {
  296. $query = $this->replaceBoundParamsWithInlineValuesInQuery($query, $params);
  297. }
  298. return parent::exec($query, array());
  299. }
  300. /**
  301. * Replaces bound parameters and their placeholders with explicit values.
  302. *
  303. * Workaround for http://bugs.php.net/36561
  304. *
  305. * @param string $query
  306. * @param array $params
  307. */
  308. protected function replaceBoundParamsWithInlineValuesInQuery($query, array $params) {
  309. foreach($params as $key => $value) {
  310. if(is_null($value)) {
  311. $value = 'NULL';
  312. }
  313. else {
  314. $value = $this->quote($value);
  315. }
  316. $re = '/([=,\(][^\\\']*)(\?)/iU';
  317. $query = preg_replace($re, "\\1 {$value}", $query, 1);
  318. }
  319. return $query;
  320. }
  321. /**
  322. * Inserts a table row with specified data.
  323. *
  324. * @param Doctrine_Table $table The table to insert data into.
  325. * @param array $values An associative array containing column-value pairs.
  326. * Values can be strings or Doctrine_Expression instances.
  327. * @return integer the number of affected rows. Boolean false if empty value array was given,
  328. */
  329. public function insert(Doctrine_Table $table, array $fields)
  330. {
  331. $identifiers = $table->getIdentifierColumnNames();
  332. $settingNullIdentifier = false;
  333. $fields = array_change_key_case($fields);
  334. foreach($identifiers as $identifier) {
  335. $lcIdentifier = strtolower($identifier);
  336. if(array_key_exists($lcIdentifier, $fields)) {
  337. if(is_null($fields[$lcIdentifier])) {
  338. $settingNullIdentifier = true;
  339. unset($fields[$lcIdentifier]);
  340. }
  341. }
  342. }
  343. // MSSQL won't allow the setting of identifier columns to null, so insert a default record and then update it
  344. if ($settingNullIdentifier) {
  345. $count = $this->exec('INSERT INTO ' . $this->quoteIdentifier($table->getTableName()) . ' DEFAULT VALUES');
  346. if(! $count) {
  347. return $count;
  348. }
  349. $id = $this->lastInsertId($table->getTableName());
  350. return $this->update($table, $fields, array($id));
  351. }
  352. return parent::insert($table, $fields);
  353. }
  354. }