PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/joomla/database/database/sqlazure.php

https://bitbucket.org/joomla/joomla-platform/
PHP | 821 lines | 370 code | 91 blank | 360 comment | 47 complexity | 8af57a08e5cb48bd45ce2da4a875c5f2 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Database
  5. *
  6. * @copyright Copyright (C) 2005 - 2011 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. jimport('joomla.database.database');
  11. jimport('joomla.utilities.string');
  12. JLoader::register('JDatabaseQuerySQLAzure', dirname(__FILE__).'/sqlazurequery.php');
  13. /**
  14. * SQL Server database driver
  15. *
  16. * @package Joomla.Platform
  17. * @subpackage Database
  18. * @since 11.1
  19. */
  20. class JDatabaseSQLAzure extends JDatabase
  21. {
  22. /**
  23. * @var string The name of the database driver.
  24. * @since 11.1
  25. */
  26. public $name = 'sqlzure';
  27. /**
  28. * @var string The character(s) used to quote SQL statement names such as table names or field names,
  29. * etc. The child classes should define this as necessary. If a single character string the
  30. * same character is used for both sides of the quoted name, else the first character will be
  31. * used for the opening quote and the second for the closing quote.
  32. * @since 11.1
  33. */
  34. protected $nameQuote;
  35. /**
  36. * @var string The null or zero representation of a timestamp for the database driver. This should be
  37. * defined in child classes to hold the appropriate value for the engine.
  38. * @since 11.1
  39. */
  40. protected $nullDate = '1900-01-01 00:00:00';
  41. /**
  42. * Constructor.
  43. *
  44. * @param array $options List of options used to configure the connection
  45. *
  46. * @return void
  47. *
  48. * @since 11.1
  49. */
  50. protected function __construct($options)
  51. {
  52. // Get some basic values from the options.
  53. $options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
  54. $options['user'] = (isset($options['user'])) ? $options['user'] : '';
  55. $options['password'] = (isset($options['password'])) ? $options['password'] : '';
  56. $options['database'] = (isset($options['database'])) ? $options['database'] : '';
  57. $options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;
  58. // Build the connection configuration array.
  59. $config = array(
  60. 'Database' => $options['database'],
  61. 'uid' => $options['user'],
  62. 'pwd' => $options['password'],
  63. 'CharacterSet' => 'UTF-8',
  64. 'ReturnDatesAsStrings' => true
  65. );
  66. // Make sure the SQLSRV extension for PHP is installed and enabled.
  67. if (!function_exists('sqlsrv_connect')) {
  68. // Legacy error handling switch based on the JError::$legacy switch.
  69. // @deprecated 11.3
  70. if (JError::$legacy) {
  71. $this->errorNum = 1;
  72. $this->errorMsg = JText::_('JLIB_DATABASE_ERROR_ADAPTER_SQLSRV');
  73. return;
  74. }
  75. else {
  76. throw new DatabaseException(JText::_('JLIB_DATABASE_ERROR_ADAPTER_SQLSRV'));
  77. }
  78. }
  79. // Attempt to connect to the server.
  80. if (!($this->connection = @ sqlsrv_connect($options['host'], $config))) {
  81. // Legacy error handling switch based on the JError::$legacy switch.
  82. // @deprecated 11.3
  83. if (JError::$legacy) {
  84. $this->errorNum = 2;
  85. $this->errorMsg = JText::_('JLIB_DATABASE_ERROR_CONNECT_SQLSRV');
  86. return;
  87. }
  88. else {
  89. throw new DatabaseException(JText::_('JLIB_DATABASE_ERROR_CONNECT_SQLSRV'));
  90. }
  91. }
  92. // Make sure that DB warnings are not returned as errors.
  93. sqlsrv_configure('WarningsReturnAsErrors', 0);
  94. // Finalize initialisation
  95. parent::__construct($options);
  96. // If auto-select is enabled select the given database.
  97. if ($options['select'] && !empty($options['database'])) {
  98. $this->select($options['database']);
  99. }
  100. }
  101. /**
  102. * Destructor.
  103. *
  104. * @return void
  105. *
  106. * @since 11.1
  107. */
  108. public function __destruct()
  109. {
  110. if (is_resource($this->connection)) {
  111. sqlsrv_close($this->connection);
  112. }
  113. }
  114. /**
  115. * Test to see if the SQLSRV connector is available.
  116. *
  117. * @return bool True on success, false otherwise.
  118. *
  119. * @since 11.1
  120. */
  121. public static function test()
  122. {
  123. return (function_exists('sqlsrv_connect'));
  124. }
  125. /**
  126. * Determines if the connection to the server is active.
  127. *
  128. * @return bool True if connected to the database engine.
  129. *
  130. * @since 11.1
  131. */
  132. public function connected()
  133. {
  134. // TODO: Run a blank query here
  135. return true;
  136. }
  137. /**
  138. * Method to get a JDate object represented as a datetime string in a format recognized by the database server.
  139. *
  140. * @param JDate $date The JDate object with which to return the datetime string.
  141. * @param bool $local True to return the date string in the local time zone, false to return it in GMT.
  142. *
  143. * @return string The datetime string in the format recognized for the database system.
  144. *
  145. * @since 11.1
  146. */
  147. public function dateToString($date, $local = false)
  148. {
  149. return $date->format('Y-m-d H:i:s', $local);
  150. }
  151. /**
  152. * Get the number of affected rows for the previous executed SQL statement.
  153. *
  154. * @return integer The number of affected rows.
  155. *
  156. * @since 11.1
  157. */
  158. public function getAffectedRows()
  159. {
  160. return sqlsrv_rows_affected($this->cursor);
  161. }
  162. /**
  163. * Method to get the database collation in use by sampling a text field of a table in the database.
  164. *
  165. * @return mixed The collation in use by the database or boolean false if not supported.
  166. *
  167. * @since 11.1
  168. */
  169. public function getCollation()
  170. {
  171. // TODO: Not fake this
  172. return 'MSSQL UTF-8 (UCS2)';
  173. }
  174. /**
  175. * Method to escape a string for usage in an SQL statement.
  176. *
  177. * The escaping for MSSQL isn't handled in the driver though that would be nice. Because of this we need
  178. * to handle the escaping ourselves. This is a first crack at it based on research done by Hooduku.
  179. *
  180. * @param string The string to be escaped.
  181. * @param bool Optional parameter to provide extra escaping.
  182. *
  183. * @return string The escaped string.
  184. *
  185. * @since 11.1
  186. */
  187. public function getEscaped($text, $extra = false)
  188. {
  189. // TODO: MSSQL Compatible escaping
  190. $result = addslashes($text);
  191. $result = str_replace("\'", "''", $result);
  192. $result = str_replace('\"', '"', $result);
  193. //$result = str_replace("\\", "''", $result);
  194. if ($extra) {
  195. // We need the below str_replace since the search in sql server doesnt recognize _ character.
  196. $result = str_replace('_', '[_]', $result);
  197. }
  198. return $result;
  199. }
  200. /**
  201. * Gets an exporter class object.
  202. *
  203. * @return JDatabaseExporterSQLAzure An exporter object.
  204. *
  205. * @since 11.1
  206. * @throws DatabaseException
  207. */
  208. public function getExporter()
  209. {
  210. // Make sure we have an exporter class for this driver.
  211. if (!class_exists('JDatabaseExporterSQLAzure')) {
  212. throw new DatabaseException(JText::_('JLIB_DATABASE_ERROR_MISSING_EXPORTER'));
  213. }
  214. $o = new JDatabaseExporterSQLAzure;
  215. $o->setDbo($this);
  216. return $o;
  217. }
  218. /**
  219. * Gets an importer class object.
  220. *
  221. * @return JDatabaseImporterSQLAzure An importer object.
  222. *
  223. * @since 11.1
  224. * @throws DatabaseException
  225. */
  226. public function getImporter()
  227. {
  228. // Make sure we have an importer class for this driver.
  229. if (!class_exists('JDatabaseImporterSQLAzure')) {
  230. throw new DatabaseException(JText::_('JLIB_DATABASE_ERROR_MISSING_IMPORTER'));
  231. }
  232. $o = new JDatabaseImporterSQLAzure;
  233. $o->setDbo($this);
  234. return $o;
  235. }
  236. /**
  237. * Get the number of returned rows for the previous executed SQL statement.
  238. *
  239. * @param resource $cursor An optional database cursor resource to extract the row count from.
  240. *
  241. * @return integer The number of returned rows.
  242. *
  243. * @since 11.1
  244. */
  245. public function getNumRows($cursor = null)
  246. {
  247. return sqlsrv_num_rows($cursor ? $cursor : $this->cursor);
  248. }
  249. /**
  250. * Get the current or query, or new JDatabaseQuery object.
  251. *
  252. * @param bool $new False to return the last query set, True to return a new JDatabaseQuery object.
  253. *
  254. * @return mixed The current value of the internal SQL variable or a new JDatabaseQuery object.
  255. *
  256. * @since 11.1
  257. * @throws DatabaseException
  258. */
  259. public function getQuery($new = false)
  260. {
  261. if ($new) {
  262. // Make sure we have a query class for this driver.
  263. if (!class_exists('JDatabaseQuerySQLAzure')) {
  264. throw new DatabaseException(JText::_('JLIB_DATABASE_ERROR_MISSING_QUERY'));
  265. }
  266. return new JDatabaseQuerySQLAzure;
  267. }
  268. else {
  269. return $this->sql;
  270. }
  271. }
  272. /**
  273. * Shows the table CREATE statement that creates the given tables.
  274. *
  275. * This is unsupported by MSSQL.
  276. *
  277. * @param mixed $tables A table name or a list of table names.
  278. *
  279. * @return array A list of the create SQL for the tables.
  280. *
  281. * @since 11.1
  282. * @throws DatabaseException
  283. */
  284. public function getTableCreate($tables)
  285. {
  286. return '';
  287. }
  288. /**
  289. * Retrieves field information about the given tables.
  290. *
  291. * @param mixed $tables A table name or a list of table names.
  292. * @param bool $typeOnly True to only return field types.
  293. *
  294. * @return array An array of fields by table.
  295. *
  296. * @since 11.1
  297. * @throws DatabaseException
  298. */
  299. public function getTableFields( $tables, $typeOnly = true )
  300. {
  301. // Initialise variables.
  302. $result = array();
  303. // Sanitize input to an array and iterate over the list.
  304. settype($tables, 'array');
  305. foreach ($tables as $table)
  306. {
  307. // Set the query to get the table fields statement.
  308. $this->setQuery(
  309. 'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' .
  310. ' FROM information_schema.columns' .
  311. ' WHERE table_name = '.$this->quote($table)
  312. );
  313. $fields = $this->loadObjectList();
  314. // If we only want the type as the value add just that to the list.
  315. if ($typeOnly) {
  316. foreach ($fields as $field)
  317. {
  318. $result[$table][$field->Field] = preg_replace("/[(0-9)]/",'', $field->Type);
  319. }
  320. }
  321. // If we want the whole field data object add that to the list.
  322. else {
  323. foreach ($fields as $field)
  324. {
  325. $result[$table][$field->Field] = $field;
  326. }
  327. }
  328. }
  329. return $result;
  330. }
  331. /**
  332. * Method to get an array of all tables in the database.
  333. *
  334. * @return array An array of all the tables in the database.
  335. *
  336. * @since 11.1
  337. * @throws DatabaseException
  338. */
  339. public function getTableList()
  340. {
  341. // Set the query to get the tables statement.
  342. $this->setQuery('SELECT name FROM sysobjects WHERE xtype = \'U\';');
  343. $tables = $this->loadResultArray();
  344. return $tables;
  345. }
  346. /**
  347. * Get the version of the database connector.
  348. *
  349. * @return string The database connector version.
  350. *
  351. * @since 11.1
  352. */
  353. public function getVersion()
  354. {
  355. // Set the query to get the version information.
  356. $this->setQuery('SELECT SERVERPROPERTY(\'ProductVersion\')');
  357. $version = $this->loadResult();
  358. return $version;
  359. }
  360. /**
  361. * Determines if the database engine supports UTF-8 character encoding.
  362. *
  363. * @return boolean True if supported.
  364. *
  365. * @since 11.1
  366. */
  367. public function hasUTF()
  368. {
  369. return true;
  370. }
  371. /**
  372. * Method to get the auto-incremented value from the last INSERT statement.
  373. *
  374. * @return integer The value of the auto-increment field from the last inserted row.
  375. *
  376. * @since 11.1
  377. */
  378. public function insertid()
  379. {
  380. // TODO: SELECT IDENTITY
  381. $this->setQuery('SELECT @@IDENTITY');
  382. return (int) $this->loadResult();
  383. }
  384. /**
  385. * Execute the SQL statement.
  386. *
  387. * @return mixed A database cursor resource on success, boolean false on failure.
  388. *
  389. * @since 11.1
  390. * @throws DatabaseException
  391. */
  392. public function query()
  393. {
  394. if (!is_resource($this->connection)) {
  395. // Legacy error handling switch based on the JError::$legacy switch.
  396. // @deprecated 11.3
  397. if (JError::$legacy) {
  398. if ($this->debug) {
  399. JError::raiseError(500, 'JDatabaseDriverSQLAzure::query: '.$this->errorNum.' - '.$this->errorMsg);
  400. }
  401. return false;
  402. }
  403. else {
  404. JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
  405. throw new DatabaseException();
  406. }
  407. }
  408. // Take a local copy so that we don't modify the original query and cause issues later
  409. $sql = $this->replacePrefix((string) $this->sql);
  410. if ($this->limit > 0 || $this->offset > 0) {
  411. $sql = $this->_limit($sql, $this->limit, $this->offset);
  412. }
  413. // If debugging is enabled then let's log the query.
  414. if ($this->debug) {
  415. // Increment the query counter and add the query to the object queue.
  416. $this->count++;
  417. $this->log[] = $sql;
  418. JLog::add($sql, JLog::DEBUG, 'databasequery');
  419. }
  420. // Reset the error values.
  421. $this->errorNum = 0;
  422. $this->errorMsg = '';
  423. // sqlsrv_num_rows requires a static or keyset cursor.
  424. if (JString::startsWith(ltrim(strtoupper($sql)), 'SELECT')) {
  425. $array = array('Scrollable' => SQLSRV_CURSOR_KEYSET);
  426. }
  427. else {
  428. $array = array();
  429. }
  430. // Execute the query.
  431. $this->cursor = sqlsrv_query($this->connection, $sql, array(), $array);
  432. // If an error occurred handle it.
  433. if (!$this->cursor) {
  434. // Populate the errors.
  435. $errors = sqlsrv_errors();
  436. $this->errorNum = $errors[0]['SQLSTATE'];
  437. $this->errorMsg = $errors[0]['message'].'SQL='.$sql;
  438. // Legacy error handling switch based on the JError::$legacy switch.
  439. // @deprecated 11.3
  440. if (JError::$legacy) {
  441. if ($this->debug) {
  442. JError::raiseError(500, 'JDatabaseDriverSQLAzure::query: '.$this->errorNum.' - '.$this->errorMsg);
  443. }
  444. return false;
  445. }
  446. else {
  447. JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'databasequery');
  448. throw new DatabaseException();
  449. }
  450. }
  451. return $this->cursor;
  452. }
  453. /**
  454. * Select a database for use.
  455. *
  456. * @param string $database The name of the database to select for use.
  457. *
  458. * @return bool True if the database was successfully selected.
  459. *
  460. * @since 11.1
  461. * @throws DatabaseException
  462. */
  463. public function select($database)
  464. {
  465. if (!$database) {
  466. return false;
  467. }
  468. if (!sqlsrv_query($this->connection, 'USE '.$database, null, array('scrollable' => SQLSRV_CURSOR_STATIC))) {
  469. // Legacy error handling switch based on the JError::$legacy switch.
  470. // @deprecated 11.3
  471. if (JError::$legacy) {
  472. $this->errorNum = 3;
  473. $this->errorMsg = JText::_('JLIB_DATABASE_ERROR_DATABASE_CONNECT');
  474. return false;
  475. }
  476. else {
  477. throw new DatabaseException(JText::_('JLIB_DATABASE_ERROR_DATABASE_CONNECT'));
  478. }
  479. }
  480. return true;
  481. }
  482. /**
  483. * Set the connection to use UTF-8 character encoding.
  484. *
  485. * @return bool True on success.
  486. *
  487. * @since 11.1
  488. */
  489. public function setUTF()
  490. {
  491. // TODO: Remove this?
  492. }
  493. /**
  494. * Method to commit a transaction.
  495. *
  496. * @return void
  497. *
  498. * @since 11.1
  499. * @throws DatabaseException
  500. */
  501. public function transactionCommit()
  502. {
  503. $this->setQuery('COMMIT TRANSACTION');
  504. $this->query();
  505. }
  506. /**
  507. * Method to roll back a transaction.
  508. *
  509. * @return void
  510. *
  511. * @since 11.1
  512. * @throws DatabaseException
  513. */
  514. public function transactionRollback()
  515. {
  516. $this->setQuery('ROLLBACK TRANSACTION');
  517. $this->query();
  518. }
  519. /**
  520. * Method to initialize a transaction.
  521. *
  522. * @return void
  523. *
  524. * @since 11.1
  525. * @throws DatabaseException
  526. */
  527. public function transactionStart()
  528. {
  529. $this->setQuery('START TRANSACTION');
  530. $this->query();
  531. }
  532. /**
  533. * Method to fetch a row from the result set cursor as an array.
  534. *
  535. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  536. *
  537. * @return mixed Either the next row from the result set or false if there are no more rows.
  538. *
  539. * @since 11.1
  540. */
  541. protected function fetchArray($cursor = null)
  542. {
  543. return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC);
  544. }
  545. /**
  546. * Method to fetch a row from the result set cursor as an associative array.
  547. *
  548. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  549. *
  550. * @return mixed Either the next row from the result set or false if there are no more rows.
  551. *
  552. * @since 11.1
  553. */
  554. protected function fetchAssoc($cursor = null)
  555. {
  556. return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC);
  557. }
  558. /**
  559. * Method to fetch a row from the result set cursor as an object.
  560. *
  561. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  562. * @param string $class The class name to use for the returned row object.
  563. *
  564. * @return mixed Either the next row from the result set or false if there are no more rows.
  565. *
  566. * @since 11.1
  567. */
  568. protected function fetchObject($cursor = null, $class = 'stdClass')
  569. {
  570. return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class);
  571. }
  572. /**
  573. * Method to free up the memory used for the result set.
  574. *
  575. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  576. *
  577. * @return void
  578. *
  579. * @since 11.1
  580. */
  581. protected function freeResult($cursor = null)
  582. {
  583. sqlsrv_free_stmt($cursor ? $cursor : $this->cursor);
  584. }
  585. /**
  586. * Diagnostic method to return explain information for a query.
  587. *
  588. * @return string The explain output.
  589. *
  590. * @since 11.1
  591. * @deprecated 11.2
  592. * @see http://msdn.microsoft.com/en-us/library/aa259203%28SQL.80%29.aspx
  593. */
  594. public function explain()
  595. {
  596. // Deprecation warning.
  597. JLog::add('JDatabase::explain() is deprecated.', JLog::WARNING, 'deprecated');
  598. // Backup the current query so we can reset it later.
  599. $backup = $this->sql;
  600. // SET SHOWPLAN_ALL ON - will make sqlsrv to show some explain of query instead of run it
  601. $this->setQuery('SET SHOWPLAN_ALL ON');
  602. $this->query();
  603. // Execute the query and get the result set cursor.
  604. $this->setQuery($backup);
  605. if (!($cursor = $this->query())) {
  606. return null;
  607. }
  608. // Build the HTML table.
  609. $first = true;
  610. $buffer = '<table id="explain-sql">';
  611. $buffer .= '<thead><tr><td colspan="99">'.$this->getQuery().'</td></tr>';
  612. while ($row = $this->fetchAssoc($cursor))
  613. {
  614. if ($first) {
  615. $buffer .= '<tr>';
  616. foreach ($row as $k => $v)
  617. {
  618. $buffer .= '<th>'.$k.'</th>';
  619. }
  620. $buffer .= '</tr></thead>';
  621. $first = false;
  622. }
  623. $buffer .= '<tbody><tr>';
  624. foreach ($row as $k => $v)
  625. {
  626. $buffer .= '<td>'.$v.'</td>';
  627. }
  628. $buffer .= '</tr>';
  629. }
  630. $buffer .= '</tbody></table>';
  631. // Free up system resources and return.
  632. $this->freeResult($cursor);
  633. // Remove the explain status.
  634. $this->setQuery('SET SHOWPLAN_ALL OFF');
  635. $this->query();
  636. // Restore the original query to it's state before we ran the explain.
  637. $this->sql = $backup;
  638. return $buffer;
  639. }
  640. /**
  641. * Execute a query batch.
  642. *
  643. * @return mixed A database resource if successful, false if not.
  644. *
  645. * @since 11.1
  646. * @deprecated 11.2
  647. */
  648. public function queryBatch($abortOnError=true, $transactionSafe = false)
  649. {
  650. // Deprecation warning.
  651. JLog::add('JDatabase::queryBatch() is deprecated.', JLog::WARNING, 'deprecated');
  652. $sql = $this->replacePrefix((string) $this->sql);
  653. $this->errorNum = 0;
  654. $this->errorMsg = '';
  655. // If the batch is meant to be transaction safe then we need to wrap it in a transaction.
  656. if ($transactionSafe) {
  657. $this->_sql = 'BEGIN TRANSACTION;'.$this->sql.'; COMMIT TRANSACTION;';
  658. }
  659. $queries = $this->splitSql($sql);
  660. $error = 0;
  661. foreach ($queries as $query)
  662. {
  663. $query = trim($query);
  664. if ($query != '') {
  665. $this->cursor = sqlsrv_query($this->connection, $query, null, array('scrollable' => SQLSRV_CURSOR_STATIC));
  666. if ($this->_debug) {
  667. $this->count++;
  668. $this->log[] = $query;
  669. }
  670. if (!$this->cursor) {
  671. $error = 1;
  672. $errors = sqlsrv_errors();
  673. $this->errorNum = $errors[0]['sqlstate'];
  674. $this->errorMsg = $errors[0]['message'];
  675. if ($abortOnError) {
  676. return $this->cursor;
  677. }
  678. }
  679. }
  680. }
  681. return $error ? false : true;
  682. }
  683. /**
  684. * Method to check and see if a field exists in a table.
  685. *
  686. * @param string $table The table in which to verify the field.
  687. * @param string $field The field to verify.
  688. *
  689. * @return bool True if the field exists in the table.
  690. *
  691. * @since 11.1
  692. */
  693. private function _checkFieldExists($table, $field)
  694. {
  695. $table = $this->replacePrefix((string) $table);
  696. $sql = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS".
  697. " WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'".
  698. " ORDER BY ORDINAL_POSITION";
  699. $this->setQuery($sql);
  700. if ($this->loadResult()) {
  701. return true;
  702. }
  703. else {
  704. return false;
  705. }
  706. }
  707. /**
  708. * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set.
  709. *
  710. * @param string $sql The SQL statement to process.
  711. * @param integer $offset The affected row offset to set.
  712. * @param integer $limit The maximum affected rows to set.
  713. *
  714. * @return string The processed SQL statement.
  715. *
  716. * @since 11.1
  717. */
  718. private function _limit($sql, $limit, $offset)
  719. {
  720. $orderBy = stristr($sql, 'ORDER BY');
  721. if (is_null($orderBy) || empty($orderBy)) {
  722. $orderBy = 'ORDER BY (select 0)';
  723. }
  724. $sql = str_ireplace($orderBy, '', $sql);
  725. $rowNumberText = ',ROW_NUMBER() OVER ('.$orderBy.') AS RowNumber FROM ';
  726. $sql = preg_replace('/\\s+FROM/','\\1 '.$rowNumberText.' ', $sql, 1);
  727. $sql = 'SELECT TOP '.$this->limit.' * FROM ('.$sql.') _myResults WHERE RowNumber > '.$this->offset;
  728. return $sql;
  729. }
  730. }