PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Joomla/Database/Sqlsrv/SqlsrvDriver.php

https://github.com/piotr-cz/joomla-framework
PHP | 1081 lines | 698 code | 99 blank | 284 comment | 40 complexity | 52d1955c8040f08ab81f57f66b6ca766 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Part of the Joomla Framework Database Package
  4. *
  5. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  6. * @license GNU General Public License version 2 or later; see LICENSE
  7. */
  8. namespace Joomla\Database\Sqlsrv;
  9. use Psr\Log;
  10. use Joomla\Database\DatabaseDriver;
  11. /**
  12. * SQL Server Database Driver
  13. *
  14. * @see http://php.net/manual/en/book.sqlsrv.php
  15. * @since 1.0
  16. */
  17. class SqlsrvDriver extends DatabaseDriver
  18. {
  19. /**
  20. * The name of the database driver.
  21. *
  22. * @var string
  23. * @since 1.0
  24. */
  25. public $name = 'sqlsrv';
  26. /**
  27. * The character(s) used to quote SQL statement names such as table names or field names,
  28. * etc. The child classes should define this as necessary. If a single character string the
  29. * same character is used for both sides of the quoted name, else the first character will be
  30. * used for the opening quote and the second for the closing quote.
  31. *
  32. * @var string
  33. * @since 1.0
  34. */
  35. protected $nameQuote;
  36. /**
  37. * The null or zero representation of a timestamp for the database driver. This should be
  38. * defined in child classes to hold the appropriate value for the engine.
  39. *
  40. * @var string
  41. * @since 1.0
  42. */
  43. protected $nullDate = '1900-01-01 00:00:00';
  44. /**
  45. * The minimum supported database version.
  46. *
  47. * @var string
  48. * @since 1.0
  49. */
  50. protected static $dbMinimum = '10.50.1600.1';
  51. /**
  52. * Test to see if the SQLSRV connector is available.
  53. *
  54. * @return boolean True on success, false otherwise.
  55. *
  56. * @since 1.0
  57. */
  58. public static function isSupported()
  59. {
  60. return (function_exists('sqlsrv_connect'));
  61. }
  62. /**
  63. * Constructor.
  64. *
  65. * @param array $options List of options used to configure the connection
  66. *
  67. * @since 1.0
  68. */
  69. public function __construct($options)
  70. {
  71. // Get some basic values from the options.
  72. $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
  73. $options['user'] = (isset($options['user'])) ? $options['user'] : '';
  74. $options['password'] = (isset($options['password'])) ? $options['password'] : '';
  75. $options['database'] = (isset($options['database'])) ? $options['database'] : '';
  76. $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;
  77. // Finalize initialisation
  78. parent::__construct($options);
  79. }
  80. /**
  81. * Destructor.
  82. *
  83. * @since 1.0
  84. */
  85. public function __destruct()
  86. {
  87. if (is_resource($this->connection))
  88. {
  89. sqlsrv_close($this->connection);
  90. }
  91. }
  92. /**
  93. * Connects to the database if needed.
  94. *
  95. * @return void Returns void if the database connected successfully.
  96. *
  97. * @since 1.0
  98. * @throws \RuntimeException
  99. */
  100. public function connect()
  101. {
  102. if ($this->connection)
  103. {
  104. return;
  105. }
  106. // Build the connection configuration array.
  107. $config = array(
  108. 'Database' => $this->options['database'],
  109. 'uid' => $this->options['user'],
  110. 'pwd' => $this->options['password'],
  111. 'CharacterSet' => 'UTF-8',
  112. 'ReturnDatesAsStrings' => true);
  113. // Make sure the SQLSRV extension for PHP is installed and enabled.
  114. if (!static::isSupported())
  115. {
  116. throw new \RuntimeException('PHP extension sqlsrv_connect is not available.');
  117. }
  118. // Attempt to connect to the server.
  119. if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config)))
  120. {
  121. throw new \RuntimeException('Database sqlsrv_connect failed');
  122. }
  123. // Make sure that DB warnings are not returned as errors.
  124. sqlsrv_configure('WarningsReturnAsErrors', 0);
  125. // If auto-select is enabled select the given database.
  126. if ($this->options['select'] && !empty($this->options['database']))
  127. {
  128. $this->select($this->options['database']);
  129. }
  130. }
  131. /**
  132. * Disconnects the database.
  133. *
  134. * @return void
  135. *
  136. * @since 1.0
  137. */
  138. public function disconnect()
  139. {
  140. // Close the connection.
  141. if (is_resource($this->connection))
  142. {
  143. sqlsrv_close($this->connection);
  144. }
  145. $this->connection = null;
  146. }
  147. /**
  148. * Get table constraints
  149. *
  150. * @param string $tableName The name of the database table.
  151. *
  152. * @return array Any constraints available for the table.
  153. *
  154. * @since 1.0
  155. */
  156. protected function getTableConstraints($tableName)
  157. {
  158. $this->connect();
  159. $this->setQuery(
  160. 'SELECT CONSTRAINT_NAME FROM' . ' INFORMATION_SCHEMA.TABLE_CONSTRAINTS' . ' WHERE TABLE_NAME = ' . $this->quote($tableName)
  161. );
  162. return $this->loadColumn();
  163. }
  164. /**
  165. * Rename constraints.
  166. *
  167. * @param array $constraints Array(strings) of table constraints
  168. * @param string $prefix A string
  169. * @param string $backup A string
  170. *
  171. * @return void
  172. *
  173. * @since 1.0
  174. */
  175. protected function renameConstraints($constraints = array(), $prefix = null, $backup = null)
  176. {
  177. $this->connect();
  178. foreach ($constraints as $constraint)
  179. {
  180. $this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint));
  181. $this->execute();
  182. }
  183. }
  184. /**
  185. * Method to escape a string for usage in an SQL statement.
  186. *
  187. * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need
  188. * to handle the escaping ourselves.
  189. *
  190. * @param string $text The string to be escaped.
  191. * @param boolean $extra Optional parameter to provide extra escaping.
  192. *
  193. * @return string The escaped string.
  194. *
  195. * @since 1.0
  196. */
  197. public function escape($text, $extra = false)
  198. {
  199. $result = addslashes($text);
  200. $result = str_replace("\'", "''", $result);
  201. $result = str_replace('\"', '"', $result);
  202. $result = str_replace('\/', '/', $result);
  203. if ($extra)
  204. {
  205. // We need the below str_replace since the search in sql server doesn't recognize _ character.
  206. $result = str_replace('_', '[_]', $result);
  207. }
  208. return $result;
  209. }
  210. /**
  211. * Determines if the connection to the server is active.
  212. *
  213. * @return boolean True if connected to the database engine.
  214. *
  215. * @since 1.0
  216. */
  217. public function connected()
  218. {
  219. // TODO: Run a blank query here
  220. return true;
  221. }
  222. /**
  223. * Drops a table from the database.
  224. *
  225. * @param string $tableName The name of the database table to drop.
  226. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
  227. *
  228. * @return SqlsrvDriver Returns this object to support chaining.
  229. *
  230. * @since 1.0
  231. */
  232. public function dropTable($tableName, $ifExists = true)
  233. {
  234. $this->connect();
  235. $query = $this->getQuery(true);
  236. if ($ifExists)
  237. {
  238. $this->setQuery(
  239. 'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName
  240. );
  241. }
  242. else
  243. {
  244. $this->setQuery('DROP TABLE ' . $tableName);
  245. }
  246. $this->execute();
  247. return $this;
  248. }
  249. /**
  250. * Get the number of affected rows for the previous executed SQL statement.
  251. *
  252. * @return integer The number of affected rows.
  253. *
  254. * @since 1.0
  255. */
  256. public function getAffectedRows()
  257. {
  258. $this->connect();
  259. return sqlsrv_rows_affected($this->cursor);
  260. }
  261. /**
  262. * Method to get the database collation in use by sampling a text field of a table in the database.
  263. *
  264. * @return mixed The collation in use by the database or boolean false if not supported.
  265. *
  266. * @since 1.0
  267. */
  268. public function getCollation()
  269. {
  270. // TODO: Not fake this
  271. return 'MSSQL UTF-8 (UCS2)';
  272. }
  273. /**
  274. * Get the number of returned rows for the previous executed SQL statement.
  275. *
  276. * @param resource $cursor An optional database cursor resource to extract the row count from.
  277. *
  278. * @return integer The number of returned rows.
  279. *
  280. * @since 1.0
  281. */
  282. public function getNumRows($cursor = null)
  283. {
  284. $this->connect();
  285. return sqlsrv_num_rows($cursor ? $cursor : $this->cursor);
  286. }
  287. /**
  288. * Retrieves field information about the given tables.
  289. *
  290. * @param mixed $table A table name
  291. * @param boolean $typeOnly True to only return field types.
  292. *
  293. * @return array An array of fields.
  294. *
  295. * @since 1.0
  296. * @throws \RuntimeException
  297. */
  298. public function getTableColumns($table, $typeOnly = true)
  299. {
  300. $result = array();
  301. $table_temp = $this->replacePrefix((string) $table);
  302. // Set the query to get the table fields statement.
  303. $this->setQuery(
  304. 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' .
  305. ' FROM information_schema.columns' . ' WHERE table_name = ' . $this->quote($table_temp)
  306. );
  307. $fields = $this->loadObjectList();
  308. // If we only want the type as the value add just that to the list.
  309. if ($typeOnly)
  310. {
  311. foreach ($fields as $field)
  312. {
  313. $result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
  314. }
  315. }
  316. else
  317. // If we want the whole field data object add that to the list.
  318. {
  319. foreach ($fields as $field)
  320. {
  321. $result[$field->Field] = $field;
  322. }
  323. }
  324. return $result;
  325. }
  326. /**
  327. * Shows the table CREATE statement that creates the given tables.
  328. *
  329. * This is unsupported by MSSQL.
  330. *
  331. * @param mixed $tables A table name or a list of table names.
  332. *
  333. * @return array A list of the create SQL for the tables.
  334. *
  335. * @since 1.0
  336. * @throws \RuntimeException
  337. */
  338. public function getTableCreate($tables)
  339. {
  340. $this->connect();
  341. return '';
  342. }
  343. /**
  344. * Get the details list of keys for a table.
  345. *
  346. * @param string $table The name of the table.
  347. *
  348. * @return array An array of the column specification for the table.
  349. *
  350. * @since 1.0
  351. * @throws \RuntimeException
  352. */
  353. public function getTableKeys($table)
  354. {
  355. $this->connect();
  356. // TODO To implement.
  357. return array();
  358. }
  359. /**
  360. * Method to get an array of all tables in the database.
  361. *
  362. * @return array An array of all the tables in the database.
  363. *
  364. * @since 1.0
  365. * @throws \RuntimeException
  366. */
  367. public function getTableList()
  368. {
  369. $this->connect();
  370. // Set the query to get the tables statement.
  371. $this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';');
  372. $tables = $this->loadColumn();
  373. return $tables;
  374. }
  375. /**
  376. * Get the version of the database connector.
  377. *
  378. * @return string The database connector version.
  379. *
  380. * @since 1.0
  381. */
  382. public function getVersion()
  383. {
  384. $this->connect();
  385. $version = sqlsrv_server_info($this->connection);
  386. return $version['SQLServerVersion'];
  387. }
  388. /**
  389. * Inserts a row into a table based on an object's properties.
  390. *
  391. * @param string $table The name of the database table to insert into.
  392. * @param object &$object A reference to an object whose public properties match the table fields.
  393. * @param string $key The name of the primary key. If provided the object property is updated.
  394. *
  395. * @return boolean True on success.
  396. *
  397. * @since 1.0
  398. * @throws \RuntimeException
  399. */
  400. public function insertObject($table, &$object, $key = null)
  401. {
  402. $fields = array();
  403. $values = array();
  404. $statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)';
  405. foreach (get_object_vars($object) as $k => $v)
  406. {
  407. // Only process non-null scalars.
  408. if (is_array($v) or is_object($v) or $v === null)
  409. {
  410. continue;
  411. }
  412. if (!$this->checkFieldExists($table, $k))
  413. {
  414. continue;
  415. }
  416. if ($k[0] == '_')
  417. {
  418. // Internal field
  419. continue;
  420. }
  421. if ($k == $key && $key == 0)
  422. {
  423. continue;
  424. }
  425. $fields[] = $this->quoteName($k);
  426. $values[] = $this->Quote($v);
  427. }
  428. // Set the query and execute the insert.
  429. $this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values)));
  430. if (!$this->execute())
  431. {
  432. return false;
  433. }
  434. $id = $this->insertid();
  435. if ($key && $id)
  436. {
  437. $object->$key = $id;
  438. }
  439. return true;
  440. }
  441. /**
  442. * Method to get the auto-incremented value from the last INSERT statement.
  443. *
  444. * @return integer The value of the auto-increment field from the last inserted row.
  445. *
  446. * @since 1.0
  447. */
  448. public function insertid()
  449. {
  450. $this->connect();
  451. // TODO: SELECT IDENTITY
  452. $this->setQuery('SELECT @@IDENTITY');
  453. return (int) $this->loadResult();
  454. }
  455. /**
  456. * Method to get the first field of the first row of the result set from the database query.
  457. *
  458. * @return mixed The return value or null if the query failed.
  459. *
  460. * @since 1.0
  461. * @throws \RuntimeException
  462. */
  463. public function loadResult()
  464. {
  465. $ret = null;
  466. // Execute the query and get the result set cursor.
  467. if (!($cursor = $this->execute()))
  468. {
  469. return null;
  470. }
  471. // Get the first row from the result set as an array.
  472. if ($row = sqlsrv_fetch_array($cursor, SQLSRV_FETCH_NUMERIC))
  473. {
  474. $ret = $row[0];
  475. }
  476. // Free up system resources and return.
  477. $this->freeResult($cursor);
  478. // For SQLServer - we need to strip slashes
  479. $ret = stripslashes($ret);
  480. return $ret;
  481. }
  482. /**
  483. * Execute the SQL statement.
  484. *
  485. * @return mixed A database cursor resource on success, boolean false on failure.
  486. *
  487. * @since 1.0
  488. * @throws \Exception
  489. * @throws \RuntimeException
  490. */
  491. public function execute()
  492. {
  493. $this->connect();
  494. if (!is_resource($this->connection))
  495. {
  496. $this->log(
  497. Log\LogLevel::ERROR,
  498. 'Database query failed (error #{code}): {message}',
  499. array('code' => $this->errorNum, 'message' => $this->errorMsg)
  500. );
  501. throw new \RuntimeException($this->errorMsg, $this->errorNum);
  502. }
  503. // Take a local copy so that we don't modify the original query and cause issues later
  504. $sql = $this->replacePrefix((string) $this->sql);
  505. if ($this->limit > 0 || $this->offset > 0)
  506. {
  507. $sql = $this->limit($sql, $this->limit, $this->offset);
  508. }
  509. // Increment the query counter.
  510. $this->count++;
  511. // If debugging is enabled then let's log the query.
  512. if ($this->debug)
  513. {
  514. // Add the query to the object queue.
  515. $this->log(
  516. Log\LogLevel::DEBUG,
  517. '{sql}',
  518. array('sql' => $sql, 'category' => 'databasequery', 'trace' => debug_backtrace())
  519. );
  520. }
  521. // Reset the error values.
  522. $this->errorNum = 0;
  523. $this->errorMsg = '';
  524. // SQLSrv_num_rows requires a static or keyset cursor.
  525. if (strncmp(ltrim(strtoupper($sql)), 'SELECT', strlen('SELECT')) == 0)
  526. {
  527. $array = array('Scrollable' => SQLSRV_CURSOR_KEYSET);
  528. }
  529. else
  530. {
  531. $array = array();
  532. }
  533. // Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
  534. $this->cursor = @sqlsrv_query($this->connection, $sql, array(), $array);
  535. // If an error occurred handle it.
  536. if (!$this->cursor)
  537. {
  538. // Check if the server was disconnected.
  539. if (!$this->connected())
  540. {
  541. try
  542. {
  543. // Attempt to reconnect.
  544. $this->connection = null;
  545. $this->connect();
  546. }
  547. catch (\RuntimeException $e)
  548. // If connect fails, ignore that exception and throw the normal exception.
  549. {
  550. // Get the error number and message.
  551. $errors = sqlsrv_errors();
  552. $this->errorNum = $errors[0]['SQLSTATE'];
  553. $this->errorMsg = $errors[0]['message'] . 'SQL=' . $sql;
  554. // Throw the normal query exception.
  555. $this->log(
  556. Log\LogLevel::ERROR,
  557. 'Database query failed (error #{code}): {message}',
  558. array('code' => $this->errorNum, 'message' => $this->errorMsg)
  559. );
  560. throw new \RuntimeException($this->errorMsg, $this->errorNum);
  561. }
  562. // Since we were able to reconnect, run the query again.
  563. return $this->execute();
  564. }
  565. else
  566. // The server was not disconnected.
  567. {
  568. // Get the error number and message.
  569. $errors = sqlsrv_errors();
  570. $this->errorNum = $errors[0]['SQLSTATE'];
  571. $this->errorMsg = $errors[0]['message'] . 'SQL=' . $sql;
  572. // Throw the normal query exception.
  573. $this->log(
  574. Log\LogLevel::ERROR,
  575. 'Database query failed (error #{code}): {message}',
  576. array('code' => $this->errorNum, 'message' => $this->errorMsg)
  577. );
  578. throw new \RuntimeException($this->errorMsg, $this->errorNum);
  579. }
  580. }
  581. return $this->cursor;
  582. }
  583. /**
  584. * This function replaces a string identifier <var>$prefix</var> with the string held is the
  585. * <var>tablePrefix</var> class variable.
  586. *
  587. * @param string $sql The SQL statement to prepare.
  588. * @param string $prefix The common table prefix.
  589. *
  590. * @return string The processed SQL statement.
  591. *
  592. * @since 1.0
  593. */
  594. public function replacePrefix($sql, $prefix = '#__')
  595. {
  596. $escaped = false;
  597. $startPos = 0;
  598. $quoteChar = '';
  599. $literal = '';
  600. $sql = trim($sql);
  601. $n = strlen($sql);
  602. while ($startPos < $n)
  603. {
  604. $ip = strpos($sql, $prefix, $startPos);
  605. if ($ip === false)
  606. {
  607. break;
  608. }
  609. $j = strpos($sql, "N'", $startPos);
  610. $k = strpos($sql, '"', $startPos);
  611. if (($k !== false) && (($k < $j) || ($j === false)))
  612. {
  613. $quoteChar = '"';
  614. $j = $k;
  615. }
  616. else
  617. {
  618. $quoteChar = "'";
  619. }
  620. if ($j === false)
  621. {
  622. $j = $n;
  623. }
  624. $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
  625. $startPos = $j;
  626. $j = $startPos + 1;
  627. if ($j >= $n)
  628. {
  629. break;
  630. }
  631. // Quote comes first, find end of quote
  632. while (true)
  633. {
  634. $k = strpos($sql, $quoteChar, $j);
  635. $escaped = false;
  636. if ($k === false)
  637. {
  638. break;
  639. }
  640. $l = $k - 1;
  641. while ($l >= 0 && $sql{$l} == '\\')
  642. {
  643. $l--;
  644. $escaped = !$escaped;
  645. }
  646. if ($escaped)
  647. {
  648. $j = $k + 1;
  649. continue;
  650. }
  651. break;
  652. }
  653. if ($k === false)
  654. {
  655. // Error in the query - no end quote; ignore it
  656. break;
  657. }
  658. $literal .= substr($sql, $startPos, $k - $startPos + 1);
  659. $startPos = $k + 1;
  660. }
  661. if ($startPos < $n)
  662. {
  663. $literal .= substr($sql, $startPos, $n - $startPos);
  664. }
  665. return $literal;
  666. }
  667. /**
  668. * Select a database for use.
  669. *
  670. * @param string $database The name of the database to select for use.
  671. *
  672. * @return boolean True if the database was successfully selected.
  673. *
  674. * @since 1.0
  675. * @throws \RuntimeException
  676. */
  677. public function select($database)
  678. {
  679. $this->connect();
  680. if (!$database)
  681. {
  682. return false;
  683. }
  684. if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC)))
  685. {
  686. throw new \RuntimeException('Could not connect to database');
  687. }
  688. return true;
  689. }
  690. /**
  691. * Set the connection to use UTF-8 character encoding.
  692. *
  693. * @return boolean True on success.
  694. *
  695. * @since 1.0
  696. */
  697. public function setUTF()
  698. {
  699. // TODO: Remove this?
  700. }
  701. /**
  702. * Method to commit a transaction.
  703. *
  704. * @param boolean $toSavepoint If true, commit to the last savepoint.
  705. *
  706. * @return void
  707. *
  708. * @since 1.0
  709. * @throws \RuntimeException
  710. */
  711. public function transactionCommit($toSavepoint = false)
  712. {
  713. $this->connect();
  714. if (!$toSavepoint || $this->transactionDepth <= 1)
  715. {
  716. if ($this->setQuery('COMMIT TRANSACTION')->execute())
  717. {
  718. $this->transactionDepth = 0;
  719. }
  720. return;
  721. }
  722. $this->transactionDepth--;
  723. }
  724. /**
  725. * Method to roll back a transaction.
  726. *
  727. * @param boolean $toSavepoint If true, rollback to the last savepoint.
  728. *
  729. * @return void
  730. *
  731. * @since 1.0
  732. * @throws \RuntimeException
  733. */
  734. public function transactionRollback($toSavepoint = false)
  735. {
  736. $this->connect();
  737. if (!$toSavepoint || $this->transactionDepth <= 1)
  738. {
  739. if ($this->setQuery('ROLLBACK TRANSACTION')->execute())
  740. {
  741. $this->transactionDepth = 0;
  742. }
  743. return;
  744. }
  745. $savepoint = 'SP_' . ($this->transactionDepth - 1);
  746. $this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint));
  747. if ($this->execute())
  748. {
  749. $this->transactionDepth--;
  750. }
  751. }
  752. /**
  753. * Method to initialize a transaction.
  754. *
  755. * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
  756. *
  757. * @return void
  758. *
  759. * @since 1.0
  760. * @throws \RuntimeException
  761. */
  762. public function transactionStart($asSavepoint = false)
  763. {
  764. $this->connect();
  765. if (!$asSavepoint || !$this->transactionDepth)
  766. {
  767. if ($this->setQuery('BEGIN TRANSACTION')->execute())
  768. {
  769. $this->transactionDepth = 1;
  770. }
  771. return;
  772. }
  773. $savepoint = 'SP_' . $this->transactionDepth;
  774. $this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint));
  775. if ($this->execute())
  776. {
  777. $this->transactionDepth++;
  778. }
  779. }
  780. /**
  781. * Method to fetch a row from the result set cursor as an array.
  782. *
  783. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  784. *
  785. * @return mixed Either the next row from the result set or false if there are no more rows.
  786. *
  787. * @since 1.0
  788. */
  789. protected function fetchArray($cursor = null)
  790. {
  791. return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC);
  792. }
  793. /**
  794. * Method to fetch a row from the result set cursor as an associative array.
  795. *
  796. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  797. *
  798. * @return mixed Either the next row from the result set or false if there are no more rows.
  799. *
  800. * @since 1.0
  801. */
  802. protected function fetchAssoc($cursor = null)
  803. {
  804. return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC);
  805. }
  806. /**
  807. * Method to fetch a row from the result set cursor as an object.
  808. *
  809. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  810. * @param string $class The class name to use for the returned row object.
  811. *
  812. * @return mixed Either the next row from the result set or false if there are no more rows.
  813. *
  814. * @since 1.0
  815. */
  816. protected function fetchObject($cursor = null, $class = 'stdClass')
  817. {
  818. return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class);
  819. }
  820. /**
  821. * Method to free up the memory used for the result set.
  822. *
  823. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  824. *
  825. * @return void
  826. *
  827. * @since 1.0
  828. */
  829. protected function freeResult($cursor = null)
  830. {
  831. sqlsrv_free_stmt($cursor ? $cursor : $this->cursor);
  832. }
  833. /**
  834. * Method to check and see if a field exists in a table.
  835. *
  836. * @param string $table The table in which to verify the field.
  837. * @param string $field The field to verify.
  838. *
  839. * @return boolean True if the field exists in the table.
  840. *
  841. * @since 1.0
  842. */
  843. protected function checkFieldExists($table, $field)
  844. {
  845. $this->connect();
  846. $table = $this->replacePrefix((string) $table);
  847. $sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS" . " WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" .
  848. " ORDER BY ORDINAL_POSITION";
  849. $this->setQuery($sql);
  850. if ($this->loadResult())
  851. {
  852. return true;
  853. }
  854. else
  855. {
  856. return false;
  857. }
  858. }
  859. /**
  860. * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set.
  861. *
  862. * @param string $sql The SQL statement to process.
  863. * @param integer $limit The maximum affected rows to set.
  864. * @param integer $offset The affected row offset to set.
  865. *
  866. * @return string The processed SQL statement.
  867. *
  868. * @since 1.0
  869. */
  870. protected function limit($sql, $limit, $offset)
  871. {
  872. $orderBy = stristr($sql, 'ORDER BY');
  873. if (is_null($orderBy) || empty($orderBy))
  874. {
  875. $orderBy = 'ORDER BY (select 0)';
  876. }
  877. $sql = str_ireplace($orderBy, '', $sql);
  878. $rowNumberText = ',ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM ';
  879. $sql = preg_replace('/\\s+FROM/', '\\1 ' . $rowNumberText . ' ', $sql, 1);
  880. $sql = 'SELECT TOP ' . $this->limit . ' * FROM (' . $sql . ') _myResults WHERE RowNumber > ' . $this->offset;
  881. return $sql;
  882. }
  883. /**
  884. * Renames a table in the database.
  885. *
  886. * @param string $oldTable The name of the table to be renamed
  887. * @param string $newTable The new name for the table.
  888. * @param string $backup Table prefix
  889. * @param string $prefix For the table - used to rename constraints in non-mysql databases
  890. *
  891. * @return SqlsrvDriver Returns this object to support chaining.
  892. *
  893. * @since 1.0
  894. * @throws \RuntimeException
  895. */
  896. public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
  897. {
  898. $constraints = array();
  899. if (!is_null($prefix) && !is_null($backup))
  900. {
  901. $constraints = $this->getTableConstraints($oldTable);
  902. }
  903. if (!empty($constraints))
  904. {
  905. $this->renameConstraints($constraints, $prefix, $backup);
  906. }
  907. $this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'");
  908. return $this->execute();
  909. }
  910. /**
  911. * Locks a table in the database.
  912. *
  913. * @param string $tableName The name of the table to lock.
  914. *
  915. * @return SqlsrvDriver Returns this object to support chaining.
  916. *
  917. * @since 1.0
  918. * @throws \RuntimeException
  919. */
  920. public function lockTable($tableName)
  921. {
  922. return $this;
  923. }
  924. /**
  925. * Unlocks tables in the database.
  926. *
  927. * @return SqlsrvDriver Returns this object to support chaining.
  928. *
  929. * @since 1.0
  930. * @throws \RuntimeException
  931. */
  932. public function unlockTables()
  933. {
  934. return $this;
  935. }
  936. }