Mssql.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. <?php
  2. /**
  3. * MS SQL layer for DBO
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake.libs.model.datasources.dbo
  16. * @since CakePHP(tm) v 0.10.5.1790
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. /**
  20. * MS SQL layer for DBO
  21. *
  22. * Long description for class
  23. *
  24. * @package cake.libs.model.datasources.dbo
  25. */
  26. class DboMssql extends DboSource {
  27. /**
  28. * Driver description
  29. *
  30. * @var string
  31. */
  32. public $description = "MS SQL DBO Driver";
  33. /**
  34. * Starting quote character for quoted identifiers
  35. *
  36. * @var string
  37. */
  38. public $startQuote = "[";
  39. /**
  40. * Ending quote character for quoted identifiers
  41. *
  42. * @var string
  43. */
  44. public $endQuote = "]";
  45. /**
  46. * Creates a map between field aliases and numeric indexes. Workaround for the
  47. * SQL Server driver's 30-character column name limitation.
  48. *
  49. * @var array
  50. */
  51. private $__fieldMappings = array();
  52. /**
  53. * Base configuration settings for MS SQL driver
  54. *
  55. * @var array
  56. */
  57. protected $_baseConfig = array(
  58. 'persistent' => true,
  59. 'host' => 'localhost',
  60. 'login' => 'root',
  61. 'password' => '',
  62. 'database' => 'cake',
  63. 'port' => '1433',
  64. );
  65. /**
  66. * MS SQL column definition
  67. *
  68. * @var array
  69. */
  70. public $columns = array(
  71. 'primary_key' => array('name' => 'IDENTITY (1, 1) NOT NULL'),
  72. 'string' => array('name' => 'varchar', 'limit' => '255'),
  73. 'text' => array('name' => 'text'),
  74. 'integer' => array('name' => 'int', 'formatter' => 'intval'),
  75. 'float' => array('name' => 'numeric', 'formatter' => 'floatval'),
  76. 'datetime' => array('name' => 'datetime', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  77. 'timestamp' => array('name' => 'timestamp', 'format' => 'Y-m-d H:i:s', 'formatter' => 'date'),
  78. 'time' => array('name' => 'datetime', 'format' => 'H:i:s', 'formatter' => 'date'),
  79. 'date' => array('name' => 'datetime', 'format' => 'Y-m-d', 'formatter' => 'date'),
  80. 'binary' => array('name' => 'image'),
  81. 'boolean' => array('name' => 'bit')
  82. );
  83. /**
  84. * Index of basic SQL commands
  85. *
  86. * @var array
  87. * @access protected
  88. */
  89. protected $_commands = array(
  90. 'begin' => 'BEGIN TRANSACTION',
  91. 'commit' => 'COMMIT',
  92. 'rollback' => 'ROLLBACK'
  93. );
  94. /**
  95. * Define if the last query had error
  96. *
  97. * @var string
  98. * @access private
  99. */
  100. private $__lastQueryHadError = false;
  101. /**
  102. * MS SQL DBO driver constructor; sets SQL Server error reporting defaults
  103. *
  104. * @param array $config Configuration data from app/config/databases.php
  105. * @return boolean True if connected successfully, false on error
  106. */
  107. function __construct($config, $autoConnect = true) {
  108. if ($autoConnect) {
  109. if (!function_exists('mssql_min_message_severity')) {
  110. trigger_error(__d('cake_dev', "PHP SQL Server interface is not installed, cannot continue. For troubleshooting information, see http://php.net/mssql/"), E_USER_WARNING);
  111. }
  112. mssql_min_message_severity(15);
  113. mssql_min_error_severity(2);
  114. }
  115. return parent::__construct($config, $autoConnect);
  116. }
  117. /**
  118. * Connects to the database using options in the given configuration array.
  119. *
  120. * @return boolean True if the database could be connected, else false
  121. */
  122. function connect() {
  123. $config = $this->config;
  124. $os = env('OS');
  125. if (!empty($os) && strpos($os, 'Windows') !== false) {
  126. $sep = ',';
  127. } else {
  128. $sep = ':';
  129. }
  130. $this->connected = false;
  131. if (is_numeric($config['port'])) {
  132. $port = $sep . $config['port']; // Port number
  133. } elseif ($config['port'] === null) {
  134. $port = ''; // No port - SQL Server 2005
  135. } else {
  136. $port = '\\' . $config['port']; // Named pipe
  137. }
  138. if (!$config['persistent']) {
  139. $this->connection = mssql_connect($config['host'] . $port, $config['login'], $config['password'], true);
  140. } else {
  141. $this->connection = mssql_pconnect($config['host'] . $port, $config['login'], $config['password']);
  142. }
  143. if (mssql_select_db($config['database'], $this->connection)) {
  144. $this->_execute("SET DATEFORMAT ymd");
  145. $this->connected = true;
  146. }
  147. return $this->connected;
  148. }
  149. /**
  150. * Check that MsSQL is installed/loaded
  151. *
  152. * @return boolean
  153. */
  154. function enabled() {
  155. return extension_loaded('mssql');
  156. }
  157. /**
  158. * Disconnects from database.
  159. *
  160. * @return boolean True if the database could be disconnected, else false
  161. */
  162. function disconnect() {
  163. @mssql_free_result($this->results);
  164. $this->connected = !@mssql_close($this->connection);
  165. return !$this->connected;
  166. }
  167. /**
  168. * Executes given SQL statement.
  169. *
  170. * @param string $sql SQL statement
  171. * @return resource Result resource identifier
  172. */
  173. protected function _execute($sql) {
  174. $result = @mssql_query($sql, $this->connection);
  175. $this->__lastQueryHadError = ($result === false);
  176. return $result;
  177. }
  178. /**
  179. * Returns an array of sources (tables) in the database.
  180. *
  181. * @return array Array of tablenames in the database
  182. */
  183. function listSources() {
  184. $cache = parent::listSources();
  185. if ($cache != null) {
  186. return $cache;
  187. }
  188. $result = $this->fetchAll('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES', false);
  189. if (!$result || empty($result)) {
  190. return array();
  191. } else {
  192. $tables = array();
  193. foreach ($result as $table) {
  194. $tables[] = $table[0]['TABLE_NAME'];
  195. }
  196. parent::listSources($tables);
  197. return $tables;
  198. }
  199. }
  200. /**
  201. * Returns an array of the fields in given table name.
  202. *
  203. * @param Model $model Model object to describe
  204. * @return array Fields in table. Keys are name and type
  205. */
  206. function describe($model) {
  207. $cache = parent::describe($model);
  208. if ($cache != null) {
  209. return $cache;
  210. }
  211. $table = $this->fullTableName($model, false);
  212. $cols = $this->fetchAll("SELECT COLUMN_NAME as Field, DATA_TYPE as Type, COL_LENGTH('" . $table . "', COLUMN_NAME) as Length, IS_NULLABLE As [Null], COLUMN_DEFAULT as [Default], COLUMNPROPERTY(OBJECT_ID('" . $table . "'), COLUMN_NAME, 'IsIdentity') as [Key], NUMERIC_SCALE as Size FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" . $table . "'", false);
  213. $fields = false;
  214. foreach ($cols as $column) {
  215. $field = $column[0]['Field'];
  216. $fields[$field] = array(
  217. 'type' => $this->column($column[0]['Type']),
  218. 'null' => (strtoupper($column[0]['Null']) == 'YES'),
  219. 'default' => preg_replace("/^[(]{1,2}'?([^')]*)?'?[)]{1,2}$/", "$1", $column[0]['Default']),
  220. 'length' => intval($column[0]['Length']),
  221. 'key' => ($column[0]['Key'] == '1') ? 'primary' : false
  222. );
  223. if ($fields[$field]['default'] === 'null') {
  224. $fields[$field]['default'] = null;
  225. } else {
  226. $this->value($fields[$field]['default'], $fields[$field]['type']);
  227. }
  228. if ($fields[$field]['key'] && $fields[$field]['type'] == 'integer') {
  229. $fields[$field]['length'] = 11;
  230. } elseif (!$fields[$field]['key']) {
  231. unset($fields[$field]['key']);
  232. }
  233. if (in_array($fields[$field]['type'], array('date', 'time', 'datetime', 'timestamp'))) {
  234. $fields[$field]['length'] = null;
  235. }
  236. }
  237. $this->__cacheDescription($this->fullTableName($model, false), $fields);
  238. return $fields;
  239. }
  240. /**
  241. * Returns a quoted and escaped string of $data for use in an SQL statement.
  242. *
  243. * @param string $data String to be prepared for use in an SQL statement
  244. * @param string $column The column into which this data will be inserted
  245. * @param boolean $safe Whether or not numeric data should be handled automagically if no column data is provided
  246. * @return string Quoted and escaped data
  247. */
  248. function value($data, $column = null, $safe = false) {
  249. $parent = parent::value($data, $column, $safe);
  250. if ($parent != null) {
  251. return $parent;
  252. }
  253. if ($data === null) {
  254. return 'NULL';
  255. }
  256. if (in_array($column, array('integer', 'float', 'binary')) && $data === '') {
  257. return 'NULL';
  258. }
  259. if ($data === '') {
  260. return "''";
  261. }
  262. switch ($column) {
  263. case 'boolean':
  264. $data = $this->boolean((bool)$data);
  265. break;
  266. default:
  267. if (get_magic_quotes_gpc()) {
  268. $data = stripslashes(str_replace("'", "''", $data));
  269. } else {
  270. $data = str_replace("'", "''", $data);
  271. }
  272. break;
  273. }
  274. if (in_array($column, array('integer', 'float', 'binary')) && is_numeric($data)) {
  275. return $data;
  276. }
  277. return "'" . $data . "'";
  278. }
  279. /**
  280. * Generates the fields list of an SQL query.
  281. *
  282. * @param Model $model
  283. * @param string $alias Alias tablename
  284. * @param mixed $fields
  285. * @return array
  286. */
  287. function fields($model, $alias = null, $fields = array(), $quote = true) {
  288. if (empty($alias)) {
  289. $alias = $model->alias;
  290. }
  291. $fields = parent::fields($model, $alias, $fields, false);
  292. $count = count($fields);
  293. if ($count >= 1 && strpos($fields[0], 'COUNT(*)') === false) {
  294. $result = array();
  295. for ($i = 0; $i < $count; $i++) {
  296. $prepend = '';
  297. if (strpos($fields[$i], 'DISTINCT') !== false) {
  298. $prepend = 'DISTINCT ';
  299. $fields[$i] = trim(str_replace('DISTINCT', '', $fields[$i]));
  300. }
  301. $fieldAlias = count($this->__fieldMappings);
  302. if (!preg_match('/\s+AS\s+/i', $fields[$i])) {
  303. if (substr($fields[$i], -1) == '*') {
  304. if (strpos($fields[$i], '.') !== false && $fields[$i] != $alias . '.*') {
  305. $build = explode('.', $fields[$i]);
  306. $AssociatedModel = $model->{$build[0]};
  307. } else {
  308. $AssociatedModel = $model;
  309. }
  310. $_fields = $this->fields($AssociatedModel, $AssociatedModel->alias, array_keys($AssociatedModel->schema()));
  311. $result = array_merge($result, $_fields);
  312. continue;
  313. }
  314. if (strpos($fields[$i], '.') === false) {
  315. $this->__fieldMappings[$alias . '__' . $fieldAlias] = $alias . '.' . $fields[$i];
  316. $fieldName = $this->name($alias . '.' . $fields[$i]);
  317. $fieldAlias = $this->name($alias . '__' . $fieldAlias);
  318. } else {
  319. $build = explode('.', $fields[$i]);
  320. $this->__fieldMappings[$build[0] . '__' . $fieldAlias] = $fields[$i];
  321. $fieldName = $this->name($build[0] . '.' . $build[1]);
  322. $fieldAlias = $this->name(preg_replace("/^\[(.+)\]$/", "$1", $build[0]) . '__' . $fieldAlias);
  323. }
  324. if ($model->getColumnType($fields[$i]) == 'datetime') {
  325. $fieldName = "CONVERT(VARCHAR(20), {$fieldName}, 20)";
  326. }
  327. $fields[$i] = "{$fieldName} AS {$fieldAlias}";
  328. }
  329. $result[] = $prepend . $fields[$i];
  330. }
  331. return $result;
  332. } else {
  333. return $fields;
  334. }
  335. }
  336. /**
  337. * Generates and executes an SQL INSERT statement for given model, fields, and values.
  338. * Removes Identity (primary key) column from update data before returning to parent, if
  339. * value is empty.
  340. *
  341. * @param Model $model
  342. * @param array $fields
  343. * @param array $values
  344. * @param mixed $conditions
  345. * @return array
  346. */
  347. function create($model, $fields = null, $values = null) {
  348. if (!empty($values)) {
  349. $fields = array_combine($fields, $values);
  350. }
  351. $primaryKey = $this->_getPrimaryKey($model);
  352. if (array_key_exists($primaryKey, $fields)) {
  353. if (empty($fields[$primaryKey])) {
  354. unset($fields[$primaryKey]);
  355. } else {
  356. $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' ON');
  357. }
  358. }
  359. $result = parent::create($model, array_keys($fields), array_values($fields));
  360. if (array_key_exists($primaryKey, $fields) && !empty($fields[$primaryKey])) {
  361. $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($model) . ' OFF');
  362. }
  363. return $result;
  364. }
  365. /**
  366. * Generates and executes an SQL UPDATE statement for given model, fields, and values.
  367. * Removes Identity (primary key) column from update data before returning to parent.
  368. *
  369. * @param Model $model
  370. * @param array $fields
  371. * @param array $values
  372. * @param mixed $conditions
  373. * @return array
  374. */
  375. function update($model, $fields = array(), $values = null, $conditions = null) {
  376. if (!empty($values)) {
  377. $fields = array_combine($fields, $values);
  378. }
  379. if (isset($fields[$model->primaryKey])) {
  380. unset($fields[$model->primaryKey]);
  381. }
  382. if (empty($fields)) {
  383. return true;
  384. }
  385. return parent::update($model, array_keys($fields), array_values($fields), $conditions);
  386. }
  387. /**
  388. * Returns a formatted error message from previous database operation.
  389. *
  390. * @return string Error message with error number
  391. */
  392. function lastError() {
  393. if ($this->__lastQueryHadError) {
  394. $error = mssql_get_last_message();
  395. if ($error && !preg_match('/contexto de la base de datos a|contesto di database|changed database|contexte de la base de don|datenbankkontext/i', $error)) {
  396. return $error;
  397. }
  398. }
  399. return null;
  400. }
  401. /**
  402. * Returns number of affected rows in previous database operation. If no previous operation exists,
  403. * this returns false.
  404. *
  405. * @return integer Number of affected rows
  406. */
  407. function lastAffected() {
  408. if ($this->_result) {
  409. return mssql_rows_affected($this->connection);
  410. }
  411. return null;
  412. }
  413. /**
  414. * Returns number of rows in previous resultset. If no previous resultset exists,
  415. * this returns false.
  416. *
  417. * @return integer Number of rows in resultset
  418. */
  419. function lastNumRows() {
  420. if ($this->_result) {
  421. return @mssql_num_rows($this->_result);
  422. }
  423. return null;
  424. }
  425. /**
  426. * Returns the ID generated from the previous INSERT operation.
  427. *
  428. * @param unknown_type $source
  429. * @return in
  430. */
  431. function lastInsertId($source = null) {
  432. $id = $this->fetchRow('SELECT SCOPE_IDENTITY() AS insertID', false);
  433. return $id[0]['insertID'];
  434. }
  435. /**
  436. * Returns a limit statement in the correct format for the particular database.
  437. *
  438. * @param integer $limit Limit of results returned
  439. * @param integer $offset Offset from which to start results
  440. * @return string SQL limit/offset statement
  441. */
  442. function limit($limit, $offset = null) {
  443. if ($limit) {
  444. $rt = '';
  445. if (!strpos(strtolower($limit), 'top') || strpos(strtolower($limit), 'top') === 0) {
  446. $rt = ' TOP';
  447. }
  448. $rt .= ' ' . $limit;
  449. if (is_int($offset) && $offset > 0) {
  450. $rt .= ' OFFSET ' . $offset;
  451. }
  452. return $rt;
  453. }
  454. return null;
  455. }
  456. /**
  457. * Converts database-layer column types to basic types
  458. *
  459. * @param string $real Real database-layer column type (i.e. "varchar(255)")
  460. * @return string Abstract column type (i.e. "string")
  461. */
  462. function column($real) {
  463. if (is_array($real)) {
  464. $col = $real['name'];
  465. if (isset($real['limit'])) {
  466. $col .= '(' . $real['limit'] . ')';
  467. }
  468. return $col;
  469. }
  470. $col = str_replace(')', '', $real);
  471. $limit = null;
  472. if (strpos($col, '(') !== false) {
  473. list($col, $limit) = explode('(', $col);
  474. }
  475. if (in_array($col, array('date', 'time', 'datetime', 'timestamp'))) {
  476. return $col;
  477. }
  478. if ($col == 'bit') {
  479. return 'boolean';
  480. }
  481. if (strpos($col, 'int') !== false) {
  482. return 'integer';
  483. }
  484. if (strpos($col, 'char') !== false) {
  485. return 'string';
  486. }
  487. if (strpos($col, 'text') !== false) {
  488. return 'text';
  489. }
  490. if (strpos($col, 'binary') !== false || $col == 'image') {
  491. return 'binary';
  492. }
  493. if (in_array($col, array('float', 'real', 'decimal', 'numeric'))) {
  494. return 'float';
  495. }
  496. return 'text';
  497. }
  498. /**
  499. * Enter description here...
  500. *
  501. * @param unknown_type $results
  502. */
  503. function resultSet(&$results) {
  504. $this->results =& $results;
  505. $this->map = array();
  506. $numFields = mssql_num_fields($results);
  507. $index = 0;
  508. $j = 0;
  509. while ($j < $numFields) {
  510. $column = mssql_field_name($results, $j);
  511. if (strpos($column, '__')) {
  512. if (isset($this->__fieldMappings[$column]) && strpos($this->__fieldMappings[$column], '.')) {
  513. $map = explode('.', $this->__fieldMappings[$column]);
  514. } elseif (isset($this->__fieldMappings[$column])) {
  515. $map = array(0, $this->__fieldMappings[$column]);
  516. } else {
  517. $map = array(0, $column);
  518. }
  519. $this->map[$index++] = $map;
  520. } else {
  521. $this->map[$index++] = array(0, $column);
  522. }
  523. $j++;
  524. }
  525. }
  526. /**
  527. * Builds final SQL statement
  528. *
  529. * @param string $type Query type
  530. * @param array $data Query data
  531. * @return string
  532. */
  533. function renderStatement($type, $data) {
  534. switch (strtolower($type)) {
  535. case 'select':
  536. extract($data);
  537. $fields = trim($fields);
  538. if (strpos($limit, 'TOP') !== false && strpos($fields, 'DISTINCT ') === 0) {
  539. $limit = 'DISTINCT ' . trim($limit);
  540. $fields = substr($fields, 9);
  541. }
  542. if (preg_match('/offset\s+([0-9]+)/i', $limit, $offset)) {
  543. $limit = preg_replace('/\s*offset.*$/i', '', $limit);
  544. preg_match('/top\s+([0-9]+)/i', $limit, $limitVal);
  545. $offset = intval($offset[1]) + intval($limitVal[1]);
  546. $rOrder = $this->__switchSort($order);
  547. list($order2, $rOrder) = array($this->__mapFields($order), $this->__mapFields($rOrder));
  548. return "SELECT * FROM (SELECT {$limit} * FROM (SELECT TOP {$offset} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}) AS Set1 {$rOrder}) AS Set2 {$order2}";
  549. } else {
  550. return "SELECT {$limit} {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order}";
  551. }
  552. break;
  553. case "schema":
  554. extract($data);
  555. foreach ($indexes as $i => $index) {
  556. if (preg_match('/PRIMARY KEY/', $index)) {
  557. unset($indexes[$i]);
  558. break;
  559. }
  560. }
  561. foreach (array('columns', 'indexes') as $var) {
  562. if (is_array(${$var})) {
  563. ${$var} = "\t" . implode(",\n\t", array_filter(${$var}));
  564. }
  565. }
  566. return "CREATE TABLE {$table} (\n{$columns});\n{$indexes}";
  567. break;
  568. default:
  569. return parent::renderStatement($type, $data);
  570. break;
  571. }
  572. }
  573. /**
  574. * Reverses the sort direction of ORDER statements to get paging offsets to work correctly
  575. *
  576. * @param string $order
  577. * @return string
  578. * @access private
  579. */
  580. function __switchSort($order) {
  581. $order = preg_replace('/\s+ASC/i', '__tmp_asc__', $order);
  582. $order = preg_replace('/\s+DESC/i', ' ASC', $order);
  583. return preg_replace('/__tmp_asc__/', ' DESC', $order);
  584. }
  585. /**
  586. * Translates field names used for filtering and sorting to shortened names using the field map
  587. *
  588. * @param string $sql A snippet of SQL representing an ORDER or WHERE statement
  589. * @return string The value of $sql with field names replaced
  590. * @access private
  591. */
  592. function __mapFields($sql) {
  593. if (empty($sql) || empty($this->__fieldMappings)) {
  594. return $sql;
  595. }
  596. foreach ($this->__fieldMappings as $key => $val) {
  597. $sql = preg_replace('/' . preg_quote($val) . '/', $this->name($key), $sql);
  598. $sql = preg_replace('/' . preg_quote($this->name($val)) . '/', $this->name($key), $sql);
  599. }
  600. return $sql;
  601. }
  602. /**
  603. * Returns an array of all result rows for a given SQL query.
  604. * Returns false if no rows matched.
  605. *
  606. * @param string $sql SQL statement
  607. * @param boolean $cache Enables returning/storing cached query results
  608. * @return array Array of resultset rows, or false if no rows matched
  609. */
  610. function read($model, $queryData = array(), $recursive = null) {
  611. $results = parent::read($model, $queryData, $recursive);
  612. $this->__fieldMappings = array();
  613. return $results;
  614. }
  615. /**
  616. * Fetches the next row from the current result set
  617. *
  618. * @return unknown
  619. */
  620. function fetchResult() {
  621. if ($row = mssql_fetch_row($this->results)) {
  622. $resultRow = array();
  623. $i = 0;
  624. foreach ($row as $index => $field) {
  625. list($table, $column) = $this->map[$index];
  626. $resultRow[$table][$column] = $row[$index];
  627. $i++;
  628. }
  629. return $resultRow;
  630. } else {
  631. return false;
  632. }
  633. }
  634. /**
  635. * Inserts multiple values into a table
  636. *
  637. * @param string $table
  638. * @param string $fields
  639. * @param array $values
  640. */
  641. public function insertMulti($table, $fields, $values) {
  642. $primaryKey = $this->_getPrimaryKey($table);
  643. $hasPrimaryKey = $primaryKey != null && (
  644. (is_array($fields) && in_array($primaryKey, $fields)
  645. || (is_string($fields) && strpos($fields, $this->startQuote . $primaryKey . $this->endQuote) !== false))
  646. );
  647. if ($hasPrimaryKey) {
  648. $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' ON');
  649. }
  650. parent::insertMulti($table, $fields, $values);
  651. if ($hasPrimaryKey) {
  652. $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' OFF');
  653. }
  654. }
  655. /**
  656. * Generate a database-native column schema string
  657. *
  658. * @param array $column An array structured like the following: array('name'=>'value', 'type'=>'value'[, options]),
  659. * where options can be 'default', 'length', or 'key'.
  660. * @return string
  661. */
  662. function buildColumn($column) {
  663. $result = preg_replace('/(int|integer)\([0-9]+\)/i', '$1', parent::buildColumn($column));
  664. if (strpos($result, 'DEFAULT NULL') !== false) {
  665. $result = str_replace('DEFAULT NULL', 'NULL', $result);
  666. } else if (array_keys($column) == array('type', 'name')) {
  667. $result .= ' NULL';
  668. }
  669. return $result;
  670. }
  671. /**
  672. * Format indexes for create table
  673. *
  674. * @param array $indexes
  675. * @param string $table
  676. * @return string
  677. */
  678. function buildIndex($indexes, $table = null) {
  679. $join = array();
  680. foreach ($indexes as $name => $value) {
  681. if ($name == 'PRIMARY') {
  682. $join[] = 'PRIMARY KEY (' . $this->name($value['column']) . ')';
  683. } else if (isset($value['unique']) && $value['unique']) {
  684. $out = "ALTER TABLE {$table} ADD CONSTRAINT {$name} UNIQUE";
  685. if (is_array($value['column'])) {
  686. $value['column'] = implode(', ', array_map(array(&$this, 'name'), $value['column']));
  687. } else {
  688. $value['column'] = $this->name($value['column']);
  689. }
  690. $out .= "({$value['column']});";
  691. $join[] = $out;
  692. }
  693. }
  694. return $join;
  695. }
  696. /**
  697. * Makes sure it will return the primary key
  698. *
  699. * @param mixed $model
  700. * @access protected
  701. * @return string
  702. */
  703. function _getPrimaryKey($model) {
  704. if (is_object($model)) {
  705. $schema = $model->schema();
  706. } else {
  707. $schema = $this->describe($model);
  708. }
  709. foreach ($schema as $field => $props) {
  710. if (isset($props['key']) && $props['key'] == 'primary') {
  711. return $field;
  712. }
  713. }
  714. return null;
  715. }
  716. }