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

/src/Joomla/Database/DatabaseDriver.php

https://github.com/piotr-cz/joomla-framework
PHP | 1695 lines | 797 code | 192 blank | 706 comment | 74 complexity | 462f5fdaf6787db9a50c98f244bedb69 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;
  9. use Psr\Log;
  10. /**
  11. * Joomla Framework Database Driver Class
  12. *
  13. * @since 1.0
  14. *
  15. * @method string q() q($text, $escape = true) Alias for quote method
  16. * @method string qn() qn($name, $as = null) Alias for quoteName method
  17. */
  18. abstract class DatabaseDriver implements DatabaseInterface, Log\LoggerAwareInterface
  19. {
  20. /**
  21. * The name of the database.
  22. *
  23. * @var string
  24. * @since 1.0
  25. */
  26. private $database;
  27. /**
  28. * The name of the database driver.
  29. *
  30. * @var string
  31. * @since 1.0
  32. */
  33. public $name;
  34. /**
  35. * The database connection resource.
  36. *
  37. * @var resource
  38. * @since 1.0
  39. */
  40. protected $connection;
  41. /**
  42. * Holds the list of available db connectors.
  43. *
  44. * @var array
  45. * @since 1.0
  46. */
  47. protected static $connectors;
  48. /**
  49. * The number of SQL statements executed by the database driver.
  50. *
  51. * @var integer
  52. * @since 1.0
  53. */
  54. protected $count = 0;
  55. /**
  56. * The database connection cursor from the last query.
  57. *
  58. * @var resource
  59. * @since 1.0
  60. */
  61. protected $cursor;
  62. /**
  63. * The database driver debugging state.
  64. *
  65. * @var boolean
  66. * @since 1.0
  67. */
  68. protected $debug = false;
  69. /**
  70. * The affected row limit for the current SQL statement.
  71. *
  72. * @var integer
  73. * @since 1.0
  74. */
  75. protected $limit = 0;
  76. /**
  77. * The character(s) used to quote SQL statement names such as table names or field names,
  78. * etc.
  79. *
  80. * The child classes should define this as necessary. If a single character string the
  81. * same character is used for both sides of the quoted name, else the first character will be
  82. * used for the opening quote and the second for the closing quote.
  83. *
  84. * @var string
  85. * @since 1.0
  86. */
  87. protected $nameQuote;
  88. /**
  89. * The null or zero representation of a timestamp for the database driver.
  90. *
  91. * This should be defined in child classes to hold the appropriate value for the engine.
  92. *
  93. * @var string
  94. * @since 1.0
  95. */
  96. protected $nullDate;
  97. /**
  98. * The affected row offset to apply for the current SQL statement.
  99. *
  100. * @var integer
  101. * @since 1.0
  102. */
  103. protected $offset = 0;
  104. /**
  105. * Passed in upon instantiation and saved.
  106. *
  107. * @var array
  108. * @since 1.0
  109. */
  110. protected $options;
  111. /**
  112. * The current SQL statement to execute.
  113. *
  114. * @var mixed
  115. * @since 1.0
  116. */
  117. protected $sql;
  118. /**
  119. * The common database table prefix.
  120. *
  121. * @var string
  122. * @since 1.0
  123. */
  124. protected $tablePrefix;
  125. /**
  126. * True if the database engine supports UTF-8 character encoding.
  127. *
  128. * @var boolean
  129. * @since 1.0
  130. */
  131. protected $utf = true;
  132. /**
  133. * The database error number.
  134. *
  135. * @var integer
  136. * @since 1.0
  137. */
  138. protected $errorNum = 0;
  139. /**
  140. * The database error message.
  141. *
  142. * @var string
  143. * @since 1.0
  144. */
  145. protected $errorMsg;
  146. /**
  147. * DatabaseDriver instances container.
  148. *
  149. * @var array
  150. * @since 1.0
  151. */
  152. protected static $instances = array();
  153. /**
  154. * The minimum supported database version.
  155. *
  156. * @var string
  157. * @since 1.0
  158. */
  159. protected static $dbMinimum;
  160. /**
  161. * The depth of the current transaction.
  162. *
  163. * @var integer
  164. * @since 1.0
  165. */
  166. protected $transactionDepth = 0;
  167. /**
  168. * A logger.
  169. *
  170. * @var Log\LoggerInterface
  171. * @since 1.0
  172. */
  173. private $logger;
  174. /**
  175. * Get a list of available database connectors. The list will only be populated with connectors that both
  176. * the class exists and the static test method returns true. This gives us the ability to have a multitude
  177. * of connector classes that are self-aware as to whether or not they are able to be used on a given system.
  178. *
  179. * @return array An array of available database connectors.
  180. *
  181. * @since 1.0
  182. */
  183. public static function getConnectors()
  184. {
  185. if (!isset(self::$connectors))
  186. {
  187. self::$connectors = array();
  188. // Get an iterator and loop trough the driver classes.
  189. $dir = __DIR__;
  190. $iterator = new \DirectoryIterator($dir);
  191. /* @var $file \DirectoryIterator */
  192. foreach ($iterator as $file)
  193. {
  194. // Only load for php files.
  195. if (!$file->isDir())
  196. {
  197. continue;
  198. }
  199. $baseName = $file->getBasename();
  200. // Derive the class name from the type.
  201. /* @var $class DatabaseDriver */
  202. $class = '\\Joomla\\Database\\' . ucfirst(strtolower($baseName)) . '\\' . ucfirst(strtolower($baseName)) . 'Driver';
  203. // If the class doesn't exist, or if it's not supported on this system, move on to the next type.
  204. if (!class_exists($class) || !($class::isSupported()))
  205. {
  206. continue;
  207. }
  208. // Everything looks good, add it to the list.
  209. self::$connectors[] = $baseName;
  210. }
  211. }
  212. return self::$connectors;
  213. }
  214. /**
  215. * Method to return a Driver instance based on the given options. There are three global options and then
  216. * the rest are specific to the database driver. The 'driver' option defines which Driver class is
  217. * used for the connection -- the default is 'mysqli'. The 'database' option determines which database is to
  218. * be used for the connection. The 'select' option determines whether the connector should automatically select
  219. * the chosen database.
  220. *
  221. * Instances are unique to the given options and new objects are only created when a unique options array is
  222. * passed into the method. This ensures that we don't end up with unnecessary database connection resources.
  223. *
  224. * @param array $options Parameters to be passed to the database driver.
  225. *
  226. * @return DatabaseDriver A database object.
  227. *
  228. * @since 1.0
  229. * @throws \RuntimeException
  230. */
  231. public static function getInstance($options = array())
  232. {
  233. // Sanitize the database connector options.
  234. $options['driver'] = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli';
  235. $options['database'] = (isset($options['database'])) ? $options['database'] : null;
  236. $options['select'] = (isset($options['select'])) ? $options['select'] : true;
  237. // Get the options signature for the database connector.
  238. $signature = md5(serialize($options));
  239. // If we already have a database connector instance for these options then just use that.
  240. if (empty(self::$instances[$signature]))
  241. {
  242. // Derive the class name from the driver.
  243. $class = '\\Joomla\\Database\\' . ucfirst(strtolower($options['driver'])) . '\\' . ucfirst(strtolower($options['driver'])) . 'Driver';
  244. // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best.
  245. if (!class_exists($class))
  246. {
  247. throw new \RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver']));
  248. }
  249. // Create our new Driver connector based on the options given.
  250. try
  251. {
  252. $instance = new $class($options);
  253. }
  254. catch (\RuntimeException $e)
  255. {
  256. throw new \RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()));
  257. }
  258. // Set the new connector to the global instances based on signature.
  259. self::$instances[$signature] = $instance;
  260. }
  261. return self::$instances[$signature];
  262. }
  263. /**
  264. * Splits a string of multiple queries into an array of individual queries.
  265. *
  266. * @param string $sql Input SQL string with which to split into individual queries.
  267. *
  268. * @return array The queries from the input string separated into an array.
  269. *
  270. * @since 1.0
  271. */
  272. public static function splitSql($sql)
  273. {
  274. $start = 0;
  275. $open = false;
  276. $char = '';
  277. $end = strlen($sql);
  278. $queries = array();
  279. for ($i = 0; $i < $end; $i++)
  280. {
  281. $current = substr($sql, $i, 1);
  282. if (($current == '"' || $current == '\''))
  283. {
  284. $n = 2;
  285. while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i)
  286. {
  287. $n++;
  288. }
  289. if ($n % 2 == 0)
  290. {
  291. if ($open)
  292. {
  293. if ($current == $char)
  294. {
  295. $open = false;
  296. $char = '';
  297. }
  298. }
  299. else
  300. {
  301. $open = true;
  302. $char = $current;
  303. }
  304. }
  305. }
  306. if (($current == ';' && !$open) || $i == $end - 1)
  307. {
  308. $queries[] = substr($sql, $start, ($i - $start + 1));
  309. $start = $i + 1;
  310. }
  311. }
  312. return $queries;
  313. }
  314. /**
  315. * Magic method to provide method alias support for quote() and quoteName().
  316. *
  317. * @param string $method The called method.
  318. * @param array $args The array of arguments passed to the method.
  319. *
  320. * @return string The aliased method's return value or null.
  321. *
  322. * @since 1.0
  323. */
  324. public function __call($method, $args)
  325. {
  326. if (empty($args))
  327. {
  328. return;
  329. }
  330. switch ($method)
  331. {
  332. case 'q':
  333. return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
  334. break;
  335. case 'qn':
  336. return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
  337. break;
  338. }
  339. }
  340. /**
  341. * Constructor.
  342. *
  343. * @param array $options List of options used to configure the connection
  344. *
  345. * @since 1.0
  346. */
  347. public function __construct($options)
  348. {
  349. // Initialise object variables.
  350. $this->database = (isset($options['database'])) ? $options['database'] : '';
  351. $this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_';
  352. $this->count = 0;
  353. $this->errorNum = 0;
  354. // Set class options.
  355. $this->options = $options;
  356. }
  357. /**
  358. * Connects to the database if needed.
  359. *
  360. * @return void Returns void if the database connected successfully.
  361. *
  362. * @since 1.0
  363. * @throws \RuntimeException
  364. */
  365. abstract public function connect();
  366. /**
  367. * Determines if the connection to the server is active.
  368. *
  369. * @return boolean True if connected to the database engine.
  370. *
  371. * @since 1.0
  372. */
  373. abstract public function connected();
  374. /**
  375. * Disconnects the database.
  376. *
  377. * @return void
  378. *
  379. * @since 1.0
  380. */
  381. abstract public function disconnect();
  382. /**
  383. * Drops a table from the database.
  384. *
  385. * @param string $table The name of the database table to drop.
  386. * @param boolean $ifExists Optionally specify that the table must exist before it is dropped.
  387. *
  388. * @return DatabaseDriver Returns this object to support chaining.
  389. *
  390. * @since 1.0
  391. * @throws \RuntimeException
  392. */
  393. abstract public function dropTable($table, $ifExists = true);
  394. /**
  395. * Escapes a string for usage in an SQL statement.
  396. *
  397. * @param string $text The string to be escaped.
  398. * @param boolean $extra Optional parameter to provide extra escaping.
  399. *
  400. * @return string The escaped string.
  401. *
  402. * @since 1.0
  403. */
  404. abstract public function escape($text, $extra = false);
  405. /**
  406. * Method to fetch a row from the result set cursor as an array.
  407. *
  408. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  409. *
  410. * @return mixed Either the next row from the result set or false if there are no more rows.
  411. *
  412. * @since 1.0
  413. */
  414. abstract protected function fetchArray($cursor = null);
  415. /**
  416. * Method to fetch a row from the result set cursor as an associative array.
  417. *
  418. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  419. *
  420. * @return mixed Either the next row from the result set or false if there are no more rows.
  421. *
  422. * @since 1.0
  423. */
  424. abstract protected function fetchAssoc($cursor = null);
  425. /**
  426. * Method to fetch a row from the result set cursor as an object.
  427. *
  428. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  429. * @param string $class The class name to use for the returned row object.
  430. *
  431. * @return mixed Either the next row from the result set or false if there are no more rows.
  432. *
  433. * @since 1.0
  434. */
  435. abstract protected function fetchObject($cursor = null, $class = 'stdClass');
  436. /**
  437. * Method to free up the memory used for the result set.
  438. *
  439. * @param mixed $cursor The optional result set cursor from which to fetch the row.
  440. *
  441. * @return void
  442. *
  443. * @since 1.0
  444. */
  445. abstract protected function freeResult($cursor = null);
  446. /**
  447. * Get the number of affected rows for the previous executed SQL statement.
  448. *
  449. * @return integer The number of affected rows.
  450. *
  451. * @since 1.0
  452. */
  453. abstract public function getAffectedRows();
  454. /**
  455. * Method to get the database collation in use by sampling a text field of a table in the database.
  456. *
  457. * @return mixed The collation in use by the database or boolean false if not supported.
  458. *
  459. * @since 1.0
  460. */
  461. abstract public function getCollation();
  462. /**
  463. * Method that provides access to the underlying database connection. Useful for when you need to call a
  464. * proprietary method such as PostgreSQL's lo_* methods.
  465. *
  466. * @return resource The underlying database connection resource.
  467. *
  468. * @since 1.0
  469. */
  470. public function getConnection()
  471. {
  472. return $this->connection;
  473. }
  474. /**
  475. * Get the total number of SQL statements executed by the database driver.
  476. *
  477. * @return integer
  478. *
  479. * @since 1.0
  480. */
  481. public function getCount()
  482. {
  483. return $this->count;
  484. }
  485. /**
  486. * Gets the name of the database used by this conneciton.
  487. *
  488. * @return string
  489. *
  490. * @since 1.0
  491. */
  492. protected function getDatabase()
  493. {
  494. return $this->database;
  495. }
  496. /**
  497. * Returns a PHP date() function compliant date format for the database driver.
  498. *
  499. * @return string The format string.
  500. *
  501. * @since 1.0
  502. */
  503. public function getDateFormat()
  504. {
  505. return 'Y-m-d H:i:s';
  506. }
  507. /**
  508. * Get the minimum supported database version.
  509. *
  510. * @return string The minimum version number for the database driver.
  511. *
  512. * @since 1.0
  513. */
  514. public function getMinimum()
  515. {
  516. return static::$dbMinimum;
  517. }
  518. /**
  519. * Get the null or zero representation of a timestamp for the database driver.
  520. *
  521. * @return string Null or zero representation of a timestamp.
  522. *
  523. * @since 1.0
  524. */
  525. public function getNullDate()
  526. {
  527. return $this->nullDate;
  528. }
  529. /**
  530. * Get the number of returned rows for the previous executed SQL statement.
  531. *
  532. * @param resource $cursor An optional database cursor resource to extract the row count from.
  533. *
  534. * @return integer The number of returned rows.
  535. *
  536. * @since 1.0
  537. */
  538. abstract public function getNumRows($cursor = null);
  539. /**
  540. * Get the common table prefix for the database driver.
  541. *
  542. * @return string The common database table prefix.
  543. *
  544. * @since 1.0
  545. */
  546. public function getPrefix()
  547. {
  548. return $this->tablePrefix;
  549. }
  550. /**
  551. * Gets an exporter class object.
  552. *
  553. * @return DatabaseExporter An exporter object.
  554. *
  555. * @since 1.0
  556. * @throws \RuntimeException
  557. */
  558. public function getExporter()
  559. {
  560. // Derive the class name from the driver.
  561. $class = '\\Joomla\\Database\\' . ucfirst($this->name) . '\\' . ucfirst($this->name) . 'Exporter';
  562. // Make sure we have an exporter class for this driver.
  563. if (!class_exists($class))
  564. {
  565. // If it doesn't exist we are at an impasse so throw an exception.
  566. throw new \RuntimeException('Database Exporter not found.');
  567. }
  568. /* @var $o DatabaseExporter */
  569. $o = new $class;
  570. $o->setDbo($this);
  571. return $o;
  572. }
  573. /**
  574. * Gets an importer class object.
  575. *
  576. * @return DatabaseImporter An importer object.
  577. *
  578. * @since 1.0
  579. * @throws \RuntimeException
  580. */
  581. public function getImporter()
  582. {
  583. // Derive the class name from the driver.
  584. $class = '\\Joomla\\Database\\' . ucfirst($this->name) . '\\' . ucfirst($this->name) . 'Importer';
  585. // Make sure we have an importer class for this driver.
  586. if (!class_exists($class))
  587. {
  588. // If it doesn't exist we are at an impasse so throw an exception.
  589. throw new \RuntimeException('Database Importer not found');
  590. }
  591. /* @var $o DatabaseImporter */
  592. $o = new $class;
  593. $o->setDbo($this);
  594. return $o;
  595. }
  596. /**
  597. * Get the current query object or a new Query object.
  598. *
  599. * @param boolean $new False to return the current query object, True to return a new Query object.
  600. *
  601. * @return DatabaseQuery The current query object or a new object extending the Query class.
  602. *
  603. * @since 1.0
  604. * @throws \RuntimeException
  605. */
  606. public function getQuery($new = false)
  607. {
  608. if ($new)
  609. {
  610. // Derive the class name from the driver.
  611. $class = '\\Joomla\\Database\\' . ucfirst($this->name) . '\\' . ucfirst($this->name) . 'Query';
  612. // Make sure we have a query class for this driver.
  613. if (!class_exists($class))
  614. {
  615. // If it doesn't exist we are at an impasse so throw an exception.
  616. throw new \RuntimeException('Database Query Class not found.');
  617. }
  618. return new $class($this);
  619. }
  620. else
  621. {
  622. return $this->sql;
  623. }
  624. }
  625. /**
  626. * Get a new iterator on the current query.
  627. *
  628. * @param string $column An option column to use as the iterator key.
  629. * @param string $class The class of object that is returned.
  630. *
  631. * @return DatabaseIterator A new database iterator.
  632. *
  633. * @since 1.0
  634. * @throws \RuntimeException
  635. */
  636. public function getIterator($column = null, $class = '\\stdClass')
  637. {
  638. // Derive the class name from the driver.
  639. $iteratorClass = '\\Joomla\\Database\\' . ucfirst($this->name) . '\\' . ucfirst($this->name) . 'Iterator';
  640. // Make sure we have an iterator class for this driver.
  641. if (!class_exists($iteratorClass))
  642. {
  643. // If it doesn't exist we are at an impasse so throw an exception.
  644. throw new \RuntimeException(sprintf('class *%s* is not defined', $iteratorClass));
  645. }
  646. // Return a new iterator
  647. return new $iteratorClass($this->execute(), $column, $class);
  648. }
  649. /**
  650. * Retrieves field information about the given tables.
  651. *
  652. * @param string $table The name of the database table.
  653. * @param boolean $typeOnly True (default) to only return field types.
  654. *
  655. * @return array An array of fields by table.
  656. *
  657. * @since 1.0
  658. * @throws \RuntimeException
  659. */
  660. abstract public function getTableColumns($table, $typeOnly = true);
  661. /**
  662. * Shows the table CREATE statement that creates the given tables.
  663. *
  664. * @param mixed $tables A table name or a list of table names.
  665. *
  666. * @return array A list of the create SQL for the tables.
  667. *
  668. * @since 1.0
  669. * @throws \RuntimeException
  670. */
  671. abstract public function getTableCreate($tables);
  672. /**
  673. * Retrieves field information about the given tables.
  674. *
  675. * @param mixed $tables A table name or a list of table names.
  676. *
  677. * @return array An array of keys for the table(s).
  678. *
  679. * @since 1.0
  680. * @throws \RuntimeException
  681. */
  682. abstract public function getTableKeys($tables);
  683. /**
  684. * Method to get an array of all tables in the database.
  685. *
  686. * @return array An array of all the tables in the database.
  687. *
  688. * @since 1.0
  689. * @throws \RuntimeException
  690. */
  691. abstract public function getTableList();
  692. /**
  693. * Determine whether or not the database engine supports UTF-8 character encoding.
  694. *
  695. * @return boolean True if the database engine supports UTF-8 character encoding.
  696. *
  697. * @since 1.0
  698. */
  699. public function hasUTFSupport()
  700. {
  701. return $this->utf;
  702. }
  703. /**
  704. * Get the version of the database connector
  705. *
  706. * @return string The database connector version.
  707. *
  708. * @since 1.0
  709. */
  710. abstract public function getVersion();
  711. /**
  712. * Method to get the auto-incremented value from the last INSERT statement.
  713. *
  714. * @return mixed The value of the auto-increment field from the last inserted row.
  715. *
  716. * @since 1.0
  717. */
  718. abstract public function insertid();
  719. /**
  720. * Inserts a row into a table based on an object's properties.
  721. *
  722. * @param string $table The name of the database table to insert into.
  723. * @param object &$object A reference to an object whose public properties match the table fields.
  724. * @param string $key The name of the primary key. If provided the object property is updated.
  725. *
  726. * @return boolean True on success.
  727. *
  728. * @since 1.0
  729. * @throws \RuntimeException
  730. */
  731. public function insertObject($table, &$object, $key = null)
  732. {
  733. $fields = array();
  734. $values = array();
  735. // Iterate over the object variables to build the query fields and values.
  736. foreach (get_object_vars($object) as $k => $v)
  737. {
  738. // Only process non-null scalars.
  739. if (is_array($v) or is_object($v) or $v === null)
  740. {
  741. continue;
  742. }
  743. // Ignore any internal fields.
  744. if ($k[0] == '_')
  745. {
  746. continue;
  747. }
  748. // Prepare and sanitize the fields and values for the database query.
  749. $fields[] = $this->quoteName($k);
  750. $values[] = $this->quote($v);
  751. }
  752. // Create the base insert statement.
  753. $query = $this->getQuery(true);
  754. $query->insert($this->quoteName($table))
  755. ->columns($fields)
  756. ->values(implode(',', $values));
  757. // Set the query and execute the insert.
  758. $this->setQuery($query);
  759. if (!$this->execute())
  760. {
  761. return false;
  762. }
  763. // Update the primary key if it exists.
  764. $id = $this->insertid();
  765. if ($key && $id && is_string($key))
  766. {
  767. $object->$key = $id;
  768. }
  769. return true;
  770. }
  771. /**
  772. * Method to check whether the installed database version is supported by the database driver
  773. *
  774. * @return boolean True if the database version is supported
  775. *
  776. * @since 1.0
  777. */
  778. public function isMinimumVersion()
  779. {
  780. return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
  781. }
  782. /**
  783. * Method to get the first row of the result set from the database query as an associative array
  784. * of ['field_name' => 'row_value'].
  785. *
  786. * @return mixed The return value or null if the query failed.
  787. *
  788. * @since 1.0
  789. * @throws \RuntimeException
  790. */
  791. public function loadAssoc()
  792. {
  793. $this->connect();
  794. $ret = null;
  795. // Execute the query and get the result set cursor.
  796. if (!($cursor = $this->execute()))
  797. {
  798. return null;
  799. }
  800. // Get the first row from the result set as an associative array.
  801. $array = $this->fetchAssoc($cursor);
  802. if ($array)
  803. {
  804. $ret = $array;
  805. }
  806. // Free up system resources and return.
  807. $this->freeResult($cursor);
  808. return $ret;
  809. }
  810. /**
  811. * Method to get an array of the result set rows from the database query where each row is an associative array
  812. * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to
  813. * a sequential numeric array.
  814. *
  815. * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
  816. * behavior and should be avoided.
  817. *
  818. * @param string $key The name of a field on which to key the result array.
  819. * @param string $column An optional column name. Instead of the whole row, only this column value will be in
  820. * the result array.
  821. *
  822. * @return mixed The return value or null if the query failed.
  823. *
  824. * @since 1.0
  825. * @throws \RuntimeException
  826. */
  827. public function loadAssocList($key = null, $column = null)
  828. {
  829. $this->connect();
  830. $array = array();
  831. // Execute the query and get the result set cursor.
  832. if (!($cursor = $this->execute()))
  833. {
  834. return null;
  835. }
  836. // Get all of the rows from the result set.
  837. while ($row = $this->fetchAssoc($cursor))
  838. {
  839. $value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;
  840. if ($key)
  841. {
  842. $array[$row[$key]] = $value;
  843. }
  844. else
  845. {
  846. $array[] = $value;
  847. }
  848. }
  849. // Free up system resources and return.
  850. $this->freeResult($cursor);
  851. return $array;
  852. }
  853. /**
  854. * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
  855. * the database query.
  856. *
  857. * @param integer $offset The row offset to use to build the result array.
  858. *
  859. * @return mixed The return value or null if the query failed.
  860. *
  861. * @since 1.0
  862. * @throws \RuntimeException
  863. */
  864. public function loadColumn($offset = 0)
  865. {
  866. $this->connect();
  867. $array = array();
  868. // Execute the query and get the result set cursor.
  869. if (!($cursor = $this->execute()))
  870. {
  871. return null;
  872. }
  873. // Get all of the rows from the result set as arrays.
  874. while ($row = $this->fetchArray($cursor))
  875. {
  876. $array[] = $row[$offset];
  877. }
  878. // Free up system resources and return.
  879. $this->freeResult($cursor);
  880. return $array;
  881. }
  882. /**
  883. * Method to get the first row of the result set from the database query as an object.
  884. *
  885. * @param string $class The class name to use for the returned row object.
  886. *
  887. * @return mixed The return value or null if the query failed.
  888. *
  889. * @since 1.0
  890. * @throws \RuntimeException
  891. */
  892. public function loadObject($class = 'stdClass')
  893. {
  894. $this->connect();
  895. $ret = null;
  896. // Execute the query and get the result set cursor.
  897. if (!($cursor = $this->execute()))
  898. {
  899. return null;
  900. }
  901. // Get the first row from the result set as an object of type $class.
  902. $object = $this->fetchObject($cursor, $class);
  903. if ($object)
  904. {
  905. $ret = $object;
  906. }
  907. // Free up system resources and return.
  908. $this->freeResult($cursor);
  909. return $ret;
  910. }
  911. /**
  912. * Method to get an array of the result set rows from the database query where each row is an object. The array
  913. * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
  914. *
  915. * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
  916. * behavior and should be avoided.
  917. *
  918. * @param string $key The name of a field on which to key the result array.
  919. * @param string $class The class name to use for the returned row objects.
  920. *
  921. * @return mixed The return value or null if the query failed.
  922. *
  923. * @since 1.0
  924. * @throws \RuntimeException
  925. */
  926. public function loadObjectList($key = '', $class = 'stdClass')
  927. {
  928. $this->connect();
  929. $array = array();
  930. // Execute the query and get the result set cursor.
  931. if (!($cursor = $this->execute()))
  932. {
  933. return null;
  934. }
  935. // Get all of the rows from the result set as objects of type $class.
  936. while ($row = $this->fetchObject($cursor, $class))
  937. {
  938. if ($key)
  939. {
  940. $array[$row->$key] = $row;
  941. }
  942. else
  943. {
  944. $array[] = $row;
  945. }
  946. }
  947. // Free up system resources and return.
  948. $this->freeResult($cursor);
  949. return $array;
  950. }
  951. /**
  952. * Method to get the first field of the first row of the result set from the database query.
  953. *
  954. * @return mixed The return value or null if the query failed.
  955. *
  956. * @since 1.0
  957. * @throws \RuntimeException
  958. */
  959. public function loadResult()
  960. {
  961. $this->connect();
  962. $ret = null;
  963. // Execute the query and get the result set cursor.
  964. if (!($cursor = $this->execute()))
  965. {
  966. return null;
  967. }
  968. // Get the first row from the result set as an array.
  969. $row = $this->fetchArray($cursor);
  970. if ($row)
  971. {
  972. $ret = $row[0];
  973. }
  974. // Free up system resources and return.
  975. $this->freeResult($cursor);
  976. return $ret;
  977. }
  978. /**
  979. * Method to get the first row of the result set from the database query as an array. Columns are indexed
  980. * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
  981. *
  982. * @return mixed The return value or null if the query failed.
  983. *
  984. * @since 1.0
  985. * @throws \RuntimeException
  986. */
  987. public function loadRow()
  988. {
  989. $this->connect();
  990. $ret = null;
  991. // Execute the query and get the result set cursor.
  992. if (!($cursor = $this->execute()))
  993. {
  994. return null;
  995. }
  996. // Get the first row from the result set as an array.
  997. $row = $this->fetchArray($cursor);
  998. if ($row)
  999. {
  1000. $ret = $row;
  1001. }
  1002. // Free up system resources and return.
  1003. $this->freeResult($cursor);
  1004. return $ret;
  1005. }
  1006. /**
  1007. * Method to get an array of the result set rows from the database query where each row is an array. The array
  1008. * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
  1009. *
  1010. * NOTE: Choosing to key the result array by a non-unique field can result in unwanted
  1011. * behavior and should be avoided.
  1012. *
  1013. * @param string $key The name of a field on which to key the result array.
  1014. *
  1015. * @return mixed The return value or null if the query failed.
  1016. *
  1017. * @since 1.0
  1018. * @throws \RuntimeException
  1019. */
  1020. public function loadRowList($key = null)
  1021. {
  1022. $this->connect();
  1023. $array = array();
  1024. // Execute the query and get the result set cursor.
  1025. if (!($cursor = $this->execute()))
  1026. {
  1027. return null;
  1028. }
  1029. // Get all of the rows from the result set as arrays.
  1030. while ($row = $this->fetchArray($cursor))
  1031. {
  1032. if ($key !== null)
  1033. {
  1034. $array[$row[$key]] = $row;
  1035. }
  1036. else
  1037. {
  1038. $array[] = $row;
  1039. }
  1040. }
  1041. // Free up system resources and return.
  1042. $this->freeResult($cursor);
  1043. return $array;
  1044. }
  1045. /**
  1046. * Logs a message.
  1047. *
  1048. * @param string $level The level for the log. Use constants belonging to Psr\Log\LogLevel.
  1049. * @param string $message The message.
  1050. * @param array $context Additional context.
  1051. *
  1052. * @return DatabaseDriver Returns itself to allow chaining.
  1053. *
  1054. * @since 1.0
  1055. */
  1056. public function log($level, $message, array $context = array())
  1057. {
  1058. if ($this->logger)
  1059. {
  1060. $this->logger->log($level, $message, $context);
  1061. }
  1062. return $this;
  1063. }
  1064. /**
  1065. * Locks a table in the database.
  1066. *
  1067. * @param string $tableName The name of the table to unlock.
  1068. *
  1069. * @return DatabaseDriver Returns this object to support chaining.
  1070. *
  1071. * @since 1.0
  1072. * @throws \RuntimeException
  1073. */
  1074. abstract public function lockTable($tableName);
  1075. /**
  1076. * Quotes and optionally escapes a string to database requirements for use in database queries.
  1077. *
  1078. * @param mixed $text A string or an array of strings to quote.
  1079. * @param boolean $escape True (default) to escape the string, false to leave it unchanged.
  1080. *
  1081. * @return string The quoted input string.
  1082. *
  1083. * @note Accepting an array of strings was added in Platform 12.3.
  1084. * @since 1.0
  1085. */
  1086. public function quote($text, $escape = true)
  1087. {
  1088. if (is_array($text))
  1089. {
  1090. foreach ($text as $k => $v)
  1091. {
  1092. $text[$k] = $this->quote($v, $escape);
  1093. }
  1094. return $text;
  1095. }
  1096. else
  1097. {
  1098. return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
  1099. }
  1100. }
  1101. /**
  1102. * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
  1103. * risks and reserved word conflicts.
  1104. *
  1105. * @param mixed $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
  1106. * Each type supports dot-notation name.
  1107. * @param mixed $as The AS query part associated to $name. It can be string or array, in latter case it has to be
  1108. * same length of $name; if is null there will not be any AS part for string or array element.
  1109. *
  1110. * @return mixed The quote wrapped name, same type of $name.
  1111. *
  1112. * @since 1.0
  1113. */
  1114. public function quoteName($name, $as = null)
  1115. {
  1116. if (is_string($name))
  1117. {
  1118. $quotedName = $this->quoteNameStr(explode('.', $name));
  1119. $quotedAs = '';
  1120. if (!is_null($as))
  1121. {
  1122. settype($as, 'array');
  1123. $quotedAs .= ' AS ' . $this->quoteNameStr($as);
  1124. }
  1125. return $quotedName . $quotedAs;
  1126. }
  1127. else
  1128. {
  1129. $fin = array();
  1130. if (is_null($as))
  1131. {
  1132. foreach ($name as $str)
  1133. {
  1134. $fin[] = $this->quoteName($str);
  1135. }
  1136. }
  1137. elseif (is_array($name) && (count($name) == count($as)))
  1138. {
  1139. $count = count($name);
  1140. for ($i = 0; $i < $count; $i++)
  1141. {
  1142. $fin[] = $this->quoteName($name[$i], $as[$i]);
  1143. }
  1144. }
  1145. return $fin;
  1146. }
  1147. }
  1148. /**
  1149. * Quote strings coming from quoteName call.
  1150. *
  1151. * @param array $strArr Array of strings coming from quoteName dot-explosion.
  1152. *
  1153. * @return string Dot-imploded string of quoted parts.
  1154. *
  1155. * @since 1.0
  1156. */
  1157. protected function quoteNameStr($strArr)
  1158. {
  1159. $parts = array();
  1160. $q = $this->nameQuote;
  1161. foreach ($strArr as $part)
  1162. {
  1163. if (is_null($part))
  1164. {
  1165. continue;
  1166. }
  1167. if (strlen($q) == 1)
  1168. {
  1169. $parts[] = $q . $part . $q;
  1170. }
  1171. else
  1172. {
  1173. $parts[] = $q{0} . $part . $q{1};
  1174. }
  1175. }
  1176. return implode('.', $parts);
  1177. }
  1178. /**
  1179. * This function replaces a string identifier <var>$prefix</var> with the string held is the
  1180. * <var>tablePrefix</var> class variable.
  1181. *
  1182. * @param string $sql The SQL statement to prepare.
  1183. * @param string $prefix The common table prefix.
  1184. *
  1185. * @return string The processed SQL statement.
  1186. *
  1187. * @since 1.0
  1188. */
  1189. public function replacePrefix($sql, $prefix = '#__')
  1190. {
  1191. $escaped = false;
  1192. $startPos = 0;
  1193. $quoteChar = '';
  1194. $literal = '';
  1195. $sql = trim($sql);
  1196. $n = strlen($sql);
  1197. while ($startPos < $n)
  1198. {
  1199. $ip = strpos($sql, $prefix, $startPos);
  1200. if ($ip === false)
  1201. {
  1202. break;
  1203. }
  1204. $j = strpos($sql, "'", $startPos);
  1205. $k = strpos($sql, '"', $startPos);
  1206. if (($k !== false) && (($k < $j) || ($j === false)))
  1207. {
  1208. $quoteChar = '"';
  1209. $j = $k;
  1210. }
  1211. else
  1212. {
  1213. $quoteChar = "'";
  1214. }
  1215. if ($j === false)
  1216. {
  1217. $j = $n;
  1218. }
  1219. $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
  1220. $startPos = $j;
  1221. $j = $startPos + 1;
  1222. if ($j >= $n)
  1223. {
  1224. break;
  1225. }
  1226. // Quote comes first, find end of quote
  1227. while (true)
  1228. {
  1229. $k = strpos($sql, $quoteChar, $j);
  1230. $escaped = false;
  1231. if ($k === false)
  1232. {
  1233. break;
  1234. }
  1235. $l = $k - 1;
  1236. while ($l >= 0 && $sql{$l} == '\\')
  1237. {
  1238. $l--;
  1239. $escaped = !$escaped;
  1240. }
  1241. if ($escaped)
  1242. {
  1243. $j = $k + 1;
  1244. continue;
  1245. }
  1246. break;
  1247. }
  1248. if ($k === false)
  1249. {
  1250. // Error in the query - no end quote; ignore it
  1251. break;
  1252. }
  1253. $literal .= substr($sql, $startPos, $k - $startPos + 1);
  1254. $startPos = $k + 1;
  1255. }
  1256. if ($startPos < $n)
  1257. {
  1258. $literal .= substr($sql, $startPos, $n - $startPos);
  1259. }
  1260. return $literal;
  1261. }
  1262. /**
  1263. * Renames a table in the database.
  1264. *
  1265. * @param string $oldTable The name of the table to be renamed
  1266. * @param string $newTable The new name for the table.
  1267. * @param string $backup Table prefix
  1268. * @param string $prefix For the table - used to rename constraints in non-mysql databases
  1269. *
  1270. * @return DatabaseDriver Returns this object to support chaining.
  1271. *
  1272. * @since 1.0
  1273. * @throws \RuntimeException
  1274. */
  1275. abstract public function renameTable($oldTable, $newTable, $backup = null, $prefix = null);
  1276. /**
  1277. * Select a database for use.
  1278. *
  1279. * @param string $database The name of the database to select for use.
  1280. *
  1281. * @return boolean True if the database was successfully selected.
  1282. *
  1283. * @since 1.0
  1284. * @throws \RuntimeException
  1285. */
  1286. abstract public function select($database);
  1287. /**
  1288. * Sets the database debugging state for the driver.
  1289. *
  1290. * @param boolean $level True to enable debugging.
  1291. *
  1292. * @return boolean The old debugging level.
  1293. *
  1294. * @since 1.0
  1295. */
  1296. public function setDebug($level)
  1297. {
  1298. $previous = $this->debug;
  1299. $this->debug = (bool) $level;
  1300. return $previous;
  1301. }
  1302. /**
  1303. * Sets the SQL statement string for later execution.
  1304. *
  1305. * @param mixed $query The SQL statement to set either as a Query object or a string.
  1306. * @param integer $offset The affected row offset to set.
  1307. * @param integer $limit The maximum affected rows to set.
  1308. *
  1309. * @return DatabaseDriver This object to support method chaining.
  1310. *
  1311. * @since 1.0
  1312. */
  1313. public function setQuery($query, $offset = 0, $limit = 0)
  1314. {
  1315. $this->sql = $query;
  1316. $this->limit = (int) max(0, $limit);
  1317. $this->offset = (int) max(0, $offset);
  1318. return $this;
  1319. }
  1320. /**
  1321. * Sets a logger instance on the object
  1322. *
  1323. * @param Log\LoggerInterface $logger A PSR-3 compliant logger.
  1324. *
  1325. * @return void
  1326. *
  1327. * @since 1.0
  1328. */
  1329. public function setLogger(Log\LoggerInterface $logger)
  1330. {
  1331. $this->logger = $logger;
  1332. }
  1333. /**
  1334. * Set the connection to use UTF-8 character encoding.
  1335. *
  1336. * @return boolean True on success.
  1337. *
  1338. * @since 1.0
  1339. */
  1340. abstract public function setUTF();
  1341. /**
  1342. * Method to commit a transaction.
  1343. *
  1344. * @param boolean $toSavepoint If true, commit to the last savepoint.
  1345. *
  1346. * @return void
  1347. *
  1348. * @since 1.0
  1349. * @throws \RuntimeException
  1350. */
  1351. abstract public function transactionCommit($toSavepoint = false);
  1352. /**
  1353. * Method to roll back a transaction.
  1354. *
  1355. * @param boolean $toSavepoint If true, rollback to the last savepoint.
  1356. *
  1357. * @return void
  1358. *
  1359. * @since 1.0
  1360. * @throws \RuntimeException
  1361. */
  1362. abstract public function transactionRollback($toSavepoint = false);
  1363. /**
  1364. * Method to initialize a transaction.
  1365. *
  1366. * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created.
  1367. *
  1368. * @return void
  1369. *
  1370. * @since 1.0
  1371. * @throws \RuntimeException
  1372. */
  1373. abstract public function transactionStart($asSavepoint = false);
  1374. /**
  1375. * Method to truncate a table.
  1376. *
  1377. * @param string $table The table to truncate
  1378. *
  1379. * @return void
  1380. *
  1381. * @since 1.0
  1382. * @throws \RuntimeException
  1383. */
  1384. public function truncateTable($table)
  1385. {
  1386. $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
  1387. $this->execute();
  1388. }
  1389. /**
  1390. * Updates a row in a table based on an object's properties.
  1391. *
  1392. * @param string $table The name of the database table to update.
  1393. * @param object &$object A reference to an object whose public properties match the table fields.
  1394. * @param array $key The name of the primary key.
  1395. * @param boolean $nulls True to update null fields or false to ignore them.
  1396. *
  1397. * @return boolean True on success.
  1398. *
  1399. * @since 1.0
  1400. * @throws \RuntimeException
  1401. */
  1402. public function updateObject($table, &$object, $key, $nulls = false)
  1403. {
  1404. $fields = array();
  1405. $where = array();
  1406. if (is_string($key))
  1407. {
  1408. $key = array($key);
  1409. }
  1410. if (is_object($key))
  1411. {
  1412. $key = (array) $key;
  1413. }
  1414. // Create the base update statement.
  1415. $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';
  1416. // Iterate over the object variables to build the query fields/value pairs.
  1417. foreach (get_object_vars($object) as $k => $v)
  1418. {
  1419. // Only process scalars that are not internal fields.
  1420. if (is_array($v) or is_object($v) or $k[0] == '_')
  1421. {
  1422. continue;
  1423. }
  1424. // Set the primary key to the WHERE clause instead of a field to update.
  1425. if (in_array($k, $key))
  1426. {
  1427. $where[] = $this->quoteName($k) . '=' . $this->quote($v);
  1428. continue;
  1429. }
  1430. // Prepare and sanitize the fields and values for the database query.
  1431. if ($v === null)
  1432. {
  1433. // If the value is null and we want to update nulls then set it.
  1434. if ($nulls)
  1435. {
  1436. $val = 'NULL';
  1437. }
  1438. else
  1439. // If the value is null and we do not want to update nulls then ignore this field.
  1440. {
  1441. continue;
  1442. }
  1443. }
  1444. else
  1445. // The field is not null so we prep it for update.
  1446. {
  1447. $val = $this->quote($v);
  1448. }
  1449. // Add the field to be updated.
  1450. $fields[] = $this->quoteName($k) . '=' . $val;
  1451. }
  1452. // We don't have any fields to update.
  1453. if (empty($fields))
  1454. {
  1455. return true;
  1456. }
  1457. // Set the query and execute the update.
  1458. $this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where)));
  1459. return $this->execute();
  1460. }
  1461. /**
  1462. * Execute the SQL statement.
  1463. *
  1464. * @return mixed A database cursor resource on success, boolean false on failure.
  1465. *
  1466. * @since 1.0
  1467. * @throws \RuntimeException
  1468. */
  1469. abstract public function execute();
  1470. /**
  1471. * Unlocks tables in the database.
  1472. *
  1473. * @return DatabaseDriver Returns this object to support chaining.
  1474. *
  1475. * @since 1.0
  1476. * @throws \RuntimeException
  1477. */
  1478. public abstract function unlockTables();
  1479. }