Mssql.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Db
  17. * @subpackage Adapter
  18. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Db_Adapter_Pdo_Abstract
  24. */
  25. require_once 'Zend/Db/Adapter/Pdo/Abstract.php';
  26. /**
  27. * Class for connecting to Microsoft SQL Server databases and performing common operations.
  28. *
  29. * @category Zend
  30. * @package Zend_Db
  31. * @subpackage Adapter
  32. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  33. * @license http://framework.zend.com/license/new-bsd New BSD License
  34. */
  35. class Zend_Db_Adapter_Pdo_Mssql extends Zend_Db_Adapter_Pdo_Abstract
  36. {
  37. /**
  38. * PDO type.
  39. *
  40. * @var string
  41. */
  42. protected $_pdoType = 'mssql';
  43. /**
  44. * Keys are UPPERCASE SQL datatypes or the constants
  45. * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
  46. *
  47. * Values are:
  48. * 0 = 32-bit integer
  49. * 1 = 64-bit integer
  50. * 2 = float or decimal
  51. *
  52. * @var array Associative array of datatypes to values 0, 1, or 2.
  53. */
  54. protected $_numericDataTypes = array(
  55. Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
  56. Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
  57. Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
  58. 'INT' => Zend_Db::INT_TYPE,
  59. 'SMALLINT' => Zend_Db::INT_TYPE,
  60. 'TINYINT' => Zend_Db::INT_TYPE,
  61. 'BIGINT' => Zend_Db::BIGINT_TYPE,
  62. 'DECIMAL' => Zend_Db::FLOAT_TYPE,
  63. 'FLOAT' => Zend_Db::FLOAT_TYPE,
  64. 'MONEY' => Zend_Db::FLOAT_TYPE,
  65. 'NUMERIC' => Zend_Db::FLOAT_TYPE,
  66. 'REAL' => Zend_Db::FLOAT_TYPE,
  67. 'SMALLMONEY' => Zend_Db::FLOAT_TYPE
  68. );
  69. /**
  70. * Creates a PDO DSN for the adapter from $this->_config settings.
  71. *
  72. * @return string
  73. */
  74. protected function _dsn()
  75. {
  76. // baseline of DSN parts
  77. $dsn = $this->_config;
  78. // don't pass the username and password in the DSN
  79. unset($dsn['username']);
  80. unset($dsn['password']);
  81. unset($dsn['driver_options']);
  82. if (isset($dsn['port'])) {
  83. $seperator = ':';
  84. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  85. $seperator = ',';
  86. }
  87. $dsn['host'] .= $seperator . $dsn['port'];
  88. unset($dsn['port']);
  89. }
  90. // this driver supports multiple DSN prefixes
  91. // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
  92. if (isset($dsn['pdoType'])) {
  93. switch (strtolower($dsn['pdoType'])) {
  94. case 'freetds':
  95. case 'sybase':
  96. $this->_pdoType = 'sybase';
  97. break;
  98. case 'mssql':
  99. $this->_pdoType = 'mssql';
  100. break;
  101. case 'dblib':
  102. default:
  103. $this->_pdoType = 'dblib';
  104. break;
  105. }
  106. unset($dsn['pdoType']);
  107. }
  108. // use all remaining parts in the DSN
  109. foreach ($dsn as $key => $val) {
  110. $dsn[$key] = "$key=$val";
  111. }
  112. $dsn = $this->_pdoType . ':' . implode(';', $dsn);
  113. return $dsn;
  114. }
  115. /**
  116. * @return void
  117. */
  118. protected function _connect()
  119. {
  120. if ($this->_connection) {
  121. return;
  122. }
  123. parent::_connect();
  124. $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
  125. }
  126. /**
  127. * Begin a transaction.
  128. *
  129. * It is necessary to override the abstract PDO transaction functions here, as
  130. * the PDO driver for MSSQL does not support transactions.
  131. */
  132. protected function _beginTransaction()
  133. {
  134. $this->_connect();
  135. $this->_connection->exec('BEGIN TRANSACTION');
  136. return true;
  137. }
  138. /**
  139. * Commit a transaction.
  140. *
  141. * It is necessary to override the abstract PDO transaction functions here, as
  142. * the PDO driver for MSSQL does not support transactions.
  143. */
  144. protected function _commit()
  145. {
  146. $this->_connect();
  147. $this->_connection->exec('COMMIT TRANSACTION');
  148. return true;
  149. }
  150. /**
  151. * Roll-back a transaction.
  152. *
  153. * It is necessary to override the abstract PDO transaction functions here, as
  154. * the PDO driver for MSSQL does not support transactions.
  155. */
  156. protected function _rollBack() {
  157. $this->_connect();
  158. $this->_connection->exec('ROLLBACK TRANSACTION');
  159. return true;
  160. }
  161. /**
  162. * Returns a list of the tables in the database.
  163. *
  164. * @return array
  165. */
  166. public function listTables()
  167. {
  168. $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
  169. return $this->fetchCol($sql);
  170. }
  171. /**
  172. * Returns the column descriptions for a table.
  173. *
  174. * The return value is an associative array keyed by the column name,
  175. * as returned by the RDBMS.
  176. *
  177. * The value of each array element is an associative array
  178. * with the following keys:
  179. *
  180. * SCHEMA_NAME => string; name of database or schema
  181. * TABLE_NAME => string;
  182. * COLUMN_NAME => string; column name
  183. * COLUMN_POSITION => number; ordinal position of column in table
  184. * DATA_TYPE => string; SQL datatype name of column
  185. * DEFAULT => string; default expression of column, null if none
  186. * NULLABLE => boolean; true if column can have nulls
  187. * LENGTH => number; length of CHAR/VARCHAR
  188. * SCALE => number; scale of NUMERIC/DECIMAL
  189. * PRECISION => number; precision of NUMERIC/DECIMAL
  190. * UNSIGNED => boolean; unsigned property of an integer type
  191. * PRIMARY => boolean; true if column is part of the primary key
  192. * PRIMARY_POSITION => integer; position of column in primary key
  193. * PRIMARY_AUTO => integer; position of auto-generated column in primary key
  194. *
  195. * @todo Discover column primary key position.
  196. * @todo Discover integer unsigned property.
  197. *
  198. * @param string $tableName
  199. * @param string $schemaName OPTIONAL
  200. * @return array
  201. */
  202. public function describeTable($tableName, $schemaName = null)
  203. {
  204. /**
  205. * Discover metadata information about this table.
  206. */
  207. $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
  208. $stmt = $this->query($sql);
  209. $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
  210. $table_name = 2;
  211. $column_name = 3;
  212. $type_name = 5;
  213. $precision = 6;
  214. $length = 7;
  215. $scale = 8;
  216. $nullable = 10;
  217. $column_def = 12;
  218. $column_position = 16;
  219. /**
  220. * Discover primary key column(s) for this table.
  221. */
  222. $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
  223. $stmt = $this->query($sql);
  224. $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
  225. $primaryKeyColumn = array();
  226. $pkey_column_name = 3;
  227. $pkey_key_seq = 4;
  228. foreach ($primaryKeysResult as $pkeysRow) {
  229. $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
  230. }
  231. $desc = array();
  232. $p = 1;
  233. foreach ($result as $key => $row) {
  234. $identity = false;
  235. $words = explode(' ', $row[$type_name], 2);
  236. if (isset($words[0])) {
  237. $type = $words[0];
  238. if (isset($words[1])) {
  239. $identity = (bool) preg_match('/identity/', $words[1]);
  240. }
  241. }
  242. $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
  243. if ($isPrimary) {
  244. $primaryPosition = $primaryKeyColumn[$row[$column_name]];
  245. } else {
  246. $primaryPosition = null;
  247. }
  248. $desc[$this->foldCase($row[$column_name])] = array(
  249. 'SCHEMA_NAME' => null, // @todo
  250. 'TABLE_NAME' => $this->foldCase($row[$table_name]),
  251. 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
  252. 'COLUMN_POSITION' => (int) $row[$column_position],
  253. 'DATA_TYPE' => $type,
  254. 'DEFAULT' => $row[$column_def],
  255. 'NULLABLE' => (bool) $row[$nullable],
  256. 'LENGTH' => $row[$length],
  257. 'SCALE' => $row[$scale],
  258. 'PRECISION' => $row[$precision],
  259. 'UNSIGNED' => null, // @todo
  260. 'PRIMARY' => $isPrimary,
  261. 'PRIMARY_POSITION' => $primaryPosition,
  262. 'IDENTITY' => $identity
  263. );
  264. }
  265. return $desc;
  266. }
  267. /**
  268. * Adds an adapter-specific LIMIT clause to the SELECT statement.
  269. *
  270. * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
  271. *
  272. * @param string $sql
  273. * @param integer $count
  274. * @param integer $offset OPTIONAL
  275. * @throws Zend_Db_Adapter_Exception
  276. * @return string
  277. */
  278. public function limit($sql, $count, $offset = 0)
  279. {
  280. $count = intval($count);
  281. if ($count <= 0) {
  282. /** @see Zend_Db_Adapter_Exception */
  283. require_once 'Zend/Db/Adapter/Exception.php';
  284. throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
  285. }
  286. $offset = intval($offset);
  287. if ($offset < 0) {
  288. /** @see Zend_Db_Adapter_Exception */
  289. require_once 'Zend/Db/Adapter/Exception.php';
  290. throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
  291. }
  292. $orderby = stristr($sql, 'ORDER BY');
  293. if ($orderby !== false) {
  294. $sort = (stripos($orderby, ' desc') !== false) ? 'desc' : 'asc';
  295. $order = str_ireplace('ORDER BY', '', $orderby);
  296. $order = trim(preg_replace('/\bASC\b|\bDESC\b/i', '', $order));
  297. }
  298. $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . ($count+$offset) . ' ', $sql);
  299. $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
  300. if ($orderby !== false) {
  301. $sql .= ' ORDER BY ' . $order . ' ';
  302. $sql .= (stripos($sort, 'asc') !== false) ? 'DESC' : 'ASC';
  303. }
  304. $sql .= ') AS outer_tbl';
  305. if ($orderby !== false) {
  306. $sql .= ' ORDER BY ' . $order . ' ' . $sort;
  307. }
  308. return $sql;
  309. }
  310. /**
  311. * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
  312. *
  313. * As a convention, on RDBMS brands that support sequences
  314. * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
  315. * from the arguments and returns the last id generated by that sequence.
  316. * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
  317. * returns the last value generated for such a column, and the table name
  318. * argument is disregarded.
  319. *
  320. * Microsoft SQL Server does not support sequences, so the arguments to
  321. * this method are ignored.
  322. *
  323. * @param string $tableName OPTIONAL Name of table.
  324. * @param string $primaryKey OPTIONAL Name of primary key column.
  325. * @return string
  326. * @throws Zend_Db_Adapter_Exception
  327. */
  328. public function lastInsertId($tableName = null, $primaryKey = null)
  329. {
  330. $sql = 'SELECT SCOPE_IDENTITY()';
  331. return (int)$this->fetchOne($sql);
  332. }
  333. /**
  334. * Retrieve server version in PHP style
  335. * Pdo_Mssql doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
  336. * @return string
  337. */
  338. public function getServerVersion()
  339. {
  340. try {
  341. $stmt = $this->query("SELECT SERVERPROPERTY('productversion')");
  342. $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
  343. if (count($result)) {
  344. return $result[0][0];
  345. }
  346. return null;
  347. } catch (PDOException $e) {
  348. return null;
  349. }
  350. }
  351. }