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