Newer
Older
Dries Buytaert
committed
<?php
/**
* @file
* Core systems for the database layer.
*
* Classes required for basic functioning of the database system should be
* placed in this file. All utility functions should also be placed in this
* file only, as they cannot auto-load the way classes can.
Dries Buytaert
committed
*/
/**
* @defgroup database Database abstraction layer
* @{
* Allow the use of different database servers using the same code base.
*
* Drupal provides a database abstraction layer to provide developers with
Dries Buytaert
committed
* the ability to support multiple database servers easily. The intent of
Dries Buytaert
committed
* this layer is to preserve the syntax and power of SQL as much as possible,
* but also allow developers a way to leverage more complex functionality in
Dries Buytaert
committed
* a unified way. It also provides a structured interface for dynamically
Dries Buytaert
committed
* constructing queries when appropriate, and enforcing security checks and
* similar good practices.
*
* The system is built atop PHP's PDO (PHP Data Objects) database API and
* inherits much of its syntax and semantics.
*
* Most Drupal database SELECT queries are performed by a call to db_query() or
Angie Byron
committed
* db_query_range(). Module authors should also consider using the PagerDefault
* Extender for queries that return results that need to be presented on
* multiple pages, and the Tablesort Extender for generating appropriate queries
* for sortable tables.
Dries Buytaert
committed
*
* For example, one might wish to return a list of the most recent 10 nodes
* authored by a given user. Instead of directly issuing the SQL query
* @code
* SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10;
Dries Buytaert
committed
* @endcode
* one would instead call the Drupal functions:
* @code
* $result = db_query_range('SELECT n.nid, n.title, n.created
Dries Buytaert
committed
* FROM {node} n WHERE n.uid = :uid', 0, 10, array(':uid' => $uid));
Dries Buytaert
committed
* foreach ($result as $record) {
* // Perform operations on $node->title, etc. here.
* }
Dries Buytaert
committed
* @endcode
* Curly braces are used around "node" to provide table prefixing via
* DatabaseConnection::prefixTables(). The explicit use of a user ID is pulled
* out into an argument passed to db_query() so that SQL injection attacks
* from user input can be caught and nullified. The LIMIT syntax varies between
* database servers, so that is abstracted into db_query_range() arguments.
Dries Buytaert
committed
* Finally, note the PDO-based ability to iterate over the result set using
* foreach ().
Dries Buytaert
committed
*
Dries Buytaert
committed
* All queries are passed as a prepared statement string. A
Dries Buytaert
committed
* prepared statement is a "template" of a query that omits literal or variable
Dries Buytaert
committed
* values in favor of placeholders. The values to place into those
Dries Buytaert
committed
* placeholders are passed separately, and the database driver handles
Dries Buytaert
committed
* inserting the values into the query in a secure fashion. That means you
Dries Buytaert
committed
* should never quote or string-escape a value to be inserted into the query.
Dries Buytaert
committed
*
Dries Buytaert
committed
* There are two formats for placeholders: named and unnamed. Named placeholders
Dries Buytaert
committed
* are strongly preferred in all cases as they are more flexible and
Dries Buytaert
committed
* self-documenting. Named placeholders should start with a colon ":" and can be
* followed by one or more letters, numbers or underscores.
Dries Buytaert
committed
*
Dries Buytaert
committed
* Named placeholders begin with a colon followed by a unique string. Example:
* @code
* SELECT nid, title FROM {node} WHERE uid=:uid;
Dries Buytaert
committed
* @endcode
Dries Buytaert
committed
*
Dries Buytaert
committed
* ":uid" is a placeholder that will be replaced with a literal value when
Dries Buytaert
committed
* the query is executed. A given placeholder label cannot be repeated in a
* given query, even if the value should be the same. When using named
Dries Buytaert
committed
* placeholders, the array of arguments to the query must be an associative
* array where keys are a placeholder label (e.g., :uid) and the value is the
Dries Buytaert
committed
* corresponding value to use. The array may be in any order.
Dries Buytaert
committed
*
Dries Buytaert
committed
* Unnamed placeholders are simply a question mark. Example:
Dries Buytaert
committed
* @code
* SELECT nid, title FROM {node} WHERE uid=?;
Dries Buytaert
committed
* @endcode
Dries Buytaert
committed
*
Dries Buytaert
committed
* In this case, the array of arguments must be an indexed array of values to
Dries Buytaert
committed
* use in the exact same order as the placeholders in the query.
*
Dries Buytaert
committed
* Note that placeholders should be a "complete" value. For example, when
Dries Buytaert
committed
* running a LIKE query the SQL wildcard character, %, should be part of the
Dries Buytaert
committed
* value, not the query itself. Thus, the following is incorrect:
Dries Buytaert
committed
* @code
* SELECT nid, title FROM {node} WHERE title LIKE :title%;
Dries Buytaert
committed
* @endcode
* It should instead read:
* @code
* SELECT nid, title FROM {node} WHERE title LIKE :title;
Dries Buytaert
committed
* @endcode
Dries Buytaert
committed
* and the value for :title should include a % as appropriate. Again, note the
* lack of quotation marks around :title. Because the value is not inserted
Dries Buytaert
committed
* into the query as one big string but as an explicitly separate value, the
Dries Buytaert
committed
* database server knows where the query ends and a value begins. That is
Dries Buytaert
committed
* considerably more secure against SQL injection than trying to remember
Dries Buytaert
committed
* which values need quotation marks and string escaping and which don't.
Dries Buytaert
committed
*
Dries Buytaert
committed
* INSERT, UPDATE, and DELETE queries need special care in order to behave
Dries Buytaert
committed
* consistently across all different databases. Therefore, they use a special
* object-oriented API for defining a query structurally. For example, rather
* than:
Dries Buytaert
committed
* @code
* INSERT INTO node (nid, title, body) VALUES (1, 'my title', 'my body');
Dries Buytaert
committed
* @endcode
* one would instead write:
* @code
* $fields = array('nid' => 1, 'title' => 'my title', 'body' => 'my body');
Angie Byron
committed
* db_insert('node')->fields($fields)->execute();
Dries Buytaert
committed
* @endcode
* This method allows databases that need special data type handling to do so,
Dries Buytaert
committed
* while also allowing optimizations such as multi-insert queries. UPDATE and
Dries Buytaert
committed
* DELETE queries have a similar pattern.
*
* Drupal also supports transactions, including a transparent fallback for
* databases that do not support transactions. To start a new transaction,
* simply call $txn = db_transaction(); in your own code. The transaction will
* remain open for as long as the variable $txn remains in scope. When $txn is
* destroyed, the transaction will be committed. If your transaction is nested
* inside of another then Drupal will track each transaction and only commit
* the outer-most transaction when the last transaction object goes out out of
* scope, that is, all relevant queries completed successfully.
*
* Example:
* @code
* function my_transaction_function() {
* // The transaction opens here.
* $txn = db_transaction();
*
Dries Buytaert
committed
* try {
* $id = db_insert('example')
* ->fields(array(
* 'field1' => 'mystring',
* 'field2' => 5,
* ))
* ->execute();
*
* my_other_function($id);
*
Dries Buytaert
committed
* return $id;
* }
* catch (Exception $e) {
Dries Buytaert
committed
* // Something went wrong somewhere, so roll back now.
Dries Buytaert
committed
* $txn->rollback();
Dries Buytaert
committed
* // Log the exception to watchdog.
* watchdog_exception('type', $e);
Dries Buytaert
committed
* }
*
Dries Buytaert
committed
* // $txn goes out of scope here. Unless the transaction was rolled back, it
* // gets automatically committed here.
* }
*
* function my_other_function($id) {
* // The transaction is still open here.
*
* if ($id % 2 == 0) {
* db_update('example')
* ->condition('id', $id)
* ->fields(array('field2' => 10))
* ->execute();
* }
* }
* @endcode
Angie Byron
committed
*
* @link http://drupal.org/developing/api/database
Dries Buytaert
committed
*/
/**
* Base Database API class.
*
* This class provides a Drupal-specific extension of the PDO database
* abstraction class in PHP. Every database driver implementation must provide a
* concrete implementation of it to support special handling required by that
* database.
Dries Buytaert
committed
*
* @see http://php.net/manual/en/book.pdo.php
Dries Buytaert
committed
*/
abstract class DatabaseConnection extends PDO {
/**
* The database target this connection is for.
*
* We need this information for later auditing and logging.
*
* @var string
*/
protected $target = NULL;
/**
* The key representing this connection.
*
* The key is a unique string which identifies a database connection. A
* connection can be a single server or a cluster of master and slaves (use
* target to pick between master and slave).
*
* @var string
*/
protected $key = NULL;
/**
* The current database logging object for this connection.
*
* @var DatabaseLog
*/
protected $logger = NULL;
/**
* Tracks the number of "layers" of transactions currently active.
*
* On many databases transactions cannot nest. Instead, we track
* nested calls to transactions and collapse them into a single
* transaction.
*
Dries Buytaert
committed
* @var array
*/
Dries Buytaert
committed
protected $transactionLayers = array();
Dries Buytaert
committed
/**
* Index of what driver-specific class to use for various operations.
*
* @var array
Dries Buytaert
committed
*/
protected $driverClasses = array();
Dries Buytaert
committed
Dries Buytaert
committed
/**
* The name of the Statement class for this connection.
*
* @var string
*/
protected $statementClass = 'DatabaseStatementBase';
/**
* Whether this database connection supports transactions.
*
* @var bool
*/
protected $transactionSupport = TRUE;
Dries Buytaert
committed
/**
* Whether this database connection supports transactional DDL.
*
* Set to FALSE by default because few databases support this feature.
*
* @var bool
*/
protected $transactionalDDLSupport = FALSE;
Dries Buytaert
committed
/**
* An index used to generate unique temporary table names.
*
* @var integer
*/
protected $temporaryNameIndex = 0;
Angie Byron
committed
/**
* The connection information for this connection object.
*
* @var array
*/
protected $connectionOptions = array();
Dries Buytaert
committed
/**
* The schema object for this connection.
*
* @var object
*/
protected $schema = NULL;
Dries Buytaert
committed
/**
Angie Byron
committed
* The prefixes used by this database connection.
Dries Buytaert
committed
*
* @var array
*/
protected $prefixes = array();
/**
* List of search values for use in prefixTables().
*
* @var array
*/
protected $prefixSearch = array();
/**
* List of replacement values for use in prefixTables().
*
* @var array
*/
protected $prefixReplace = array();
Dries Buytaert
committed
function __construct($dsn, $username, $password, $driver_options = array()) {
Dries Buytaert
committed
// Initialize and prepare the connection prefix.
$this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : '');
// Because the other methods don't seem to work right.
$driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
Dries Buytaert
committed
// Call PDO::__construct and PDO::setAttribute.
Dries Buytaert
committed
parent::__construct($dsn, $username, $password, $driver_options);
Dries Buytaert
committed
// Set a specific PDOStatement class if the driver requires that.
if (!empty($this->statementClass)) {
Dries Buytaert
committed
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this)));
Dries Buytaert
committed
}
Dries Buytaert
committed
}
/**
* Returns the default query options for any given query.
Dries Buytaert
committed
*
Dries Buytaert
committed
* A given query can be customized with a number of option flags in an
* associative array:
* - target: The database "target" against which to execute a query. Valid
Dries Buytaert
committed
* values are "default" or "slave". The system will first try to open a
* connection to a database specified with the user-supplied key. If one
Dries Buytaert
committed
* is not available, it will silently fall back to the "default" target.
* If multiple databases connections are specified with the same target,
Dries Buytaert
committed
* one will be selected at random for the duration of the request.
* - fetch: This element controls how rows from a result set will be
* returned. Legal values include PDO::FETCH_ASSOC, PDO::FETCH_BOTH,
* PDO::FETCH_OBJ, PDO::FETCH_NUM, or a string representing the name of a
* class. If a string is specified, each record will be fetched into a new
* object of that class. The behavior of all other values is defined by PDO.
Dries Buytaert
committed
* See http://php.net/manual/pdostatement.fetch.php
* - return: Depending on the type of query, different return values may be
Dries Buytaert
committed
* meaningful. This directive instructs the system which type of return
* value is desired. The system will generally set the correct value
Dries Buytaert
committed
* automatically, so it is extremely rare that a module developer will ever
Dries Buytaert
committed
* need to specify this value. Setting it incorrectly will likely lead to
* unpredictable results or fatal errors. Legal values include:
* - Database::RETURN_STATEMENT: Return the prepared statement object for
* the query. This is usually only meaningful for SELECT queries, where
* the statement object is how one accesses the result set returned by the
* query.
* - Database::RETURN_AFFECTED: Return the number of rows affected by an
* UPDATE or DELETE query. Be aware that means the number of rows actually
* changed, not the number of rows matched by the WHERE clause.
* - Database::RETURN_INSERT_ID: Return the sequence ID (primary key)
* created by an INSERT statement on a table that contains a serial
* column.
* - Database::RETURN_NULL: Do not return anything, as there is no
Dries Buytaert
committed
* meaningful value to return. That is the case for INSERT queries on
Dries Buytaert
committed
* tables that do not contain a serial column.
* - throw_exception: By default, the database system will catch any errors
Dries Buytaert
committed
* on a query as an Exception, log it, and then rethrow it so that code
* further up the call chain can take an appropriate action. To suppress
* that behavior and simply return NULL on failure, set this option to
* FALSE.
Dries Buytaert
committed
*
* @return
* An array of default query options.
*/
protected function defaultOptions() {
return array(
'target' => 'default',
'fetch' => PDO::FETCH_OBJ,
'return' => Database::RETURN_STATEMENT,
'throw_exception' => TRUE,
);
}
Angie Byron
committed
/**
* Returns the connection information for this connection object.
Angie Byron
committed
*
* Note that Database::getConnectionInfo() is for requesting information
* about an arbitrary database connection that is defined. This method
* is for requesting the connection information of this specific
* open connection object.
*
* @return
* An array of the connection information. The exact list of
* properties is driver-dependent.
*/
public function getConnectionOptions() {
return $this->connectionOptions;
}
Dries Buytaert
committed
/**
Angie Byron
committed
* Set the list of prefixes used by this database connection.
Dries Buytaert
committed
*
* @param $prefix
* The prefixes, in any of the multiple forms documented in
* default.settings.php.
*/
protected function setPrefix($prefix) {
if (is_array($prefix)) {
Angie Byron
committed
$this->prefixes = $prefix + array('default' => '');
Dries Buytaert
committed
}
else {
Angie Byron
committed
$this->prefixes = array('default' => $prefix);
Dries Buytaert
committed
}
// Set up variables for use in prefixTables(). Replace table-specific
// prefixes first.
$this->prefixSearch = array();
$this->prefixReplace = array();
foreach ($this->prefixes as $key => $val) {
Angie Byron
committed
if ($key != 'default') {
$this->prefixSearch[] = '{' . $key . '}';
$this->prefixReplace[] = $val . $key;
}
}
// Then replace remaining tables with the default prefix.
$this->prefixSearch[] = '{';
Angie Byron
committed
$this->prefixReplace[] = $this->prefixes['default'];
$this->prefixSearch[] = '}';
$this->prefixReplace[] = '';
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Appends a database prefix to all tables in a query.
Dries Buytaert
committed
*
* Queries sent to Drupal should wrap all table names in curly brackets. This
* function searches for this syntax and adds Drupal's table prefix to all
Dries Buytaert
committed
* tables, allowing Drupal to coexist with other systems in the same database
* and/or schema if necessary.
Dries Buytaert
committed
*
* @param $sql
* A string containing a partial or entire SQL query.
Dries Buytaert
committed
* @return
* The properly-prefixed string.
*/
public function prefixTables($sql) {
return str_replace($this->prefixSearch, $this->prefixReplace, $sql);
Dries Buytaert
committed
}
/**
* Find the prefix for a table.
*
* This function is for when you want to know the prefix of a table. This
* is not used in prefixTables due to performance reasons.
*/
public function tablePrefix($table = 'default') {
Dries Buytaert
committed
if (isset($this->prefixes[$table])) {
return $this->prefixes[$table];
}
else {
Angie Byron
committed
return $this->prefixes['default'];
}
}
Dries Buytaert
committed
/**
* Prepares a query string and returns the prepared statement.
Dries Buytaert
committed
* This method caches prepared statements, reusing them when
Dries Buytaert
committed
* possible. It also prefixes tables names enclosed in curly-braces.
Dries Buytaert
committed
*
* @param $query
* The query string as SQL, with curly-braces surrounding the
* table names.
Dries Buytaert
committed
* @return DatabaseStatementInterface
Dries Buytaert
committed
* A PDO prepared statement ready for its execute() method.
*/
Angie Byron
committed
public function prepareQuery($query) {
$query = $this->prefixTables($query);
Angie Byron
committed
// Call PDO::prepare.
return parent::prepare($query);
Dries Buytaert
committed
}
/**
* Tells this connection object what its target value is.
*
Dries Buytaert
committed
* This is needed for logging and auditing. It's sloppy to do in the
* constructor because the constructor for child classes has a different
Dries Buytaert
committed
* signature. We therefore also ensure that this function is only ever
* called once.
*
* @param $target
Dries Buytaert
committed
* The target this connection is for. Set to NULL (default) to disable
* logging entirely.
*/
public function setTarget($target = NULL) {
if (!isset($this->target)) {
$this->target = $target;
}
}
/**
* Returns the target this connection is associated with.
*
* @return
* The target string of this connection.
*/
public function getTarget() {
return $this->target;
}
/**
* Tells this connection object what its key is.
*
* @param $target
* The key this connection is for.
*/
public function setKey($key) {
if (!isset($this->key)) {
$this->key = $key;
}
}
/**
* Returns the key this connection is associated with.
*
* @return
* The key of this connection.
*/
public function getKey() {
return $this->key;
}
/**
* Associates a logging object with this connection.
*
* @param $logger
* The logging object we want to use.
*/
public function setLogger(DatabaseLog $logger) {
$this->logger = $logger;
}
/**
* Gets the current logging object for this connection.
*
Dries Buytaert
committed
* @return DatabaseLog
Dries Buytaert
committed
* The current logging object for this connection. If there isn't one,
* NULL is returned.
*/
public function getLogger() {
return $this->logger;
}
Dries Buytaert
committed
/**
* Creates the appropriate sequence name for a given table and serial field.
Dries Buytaert
committed
*
Dries Buytaert
committed
* This information is exposed to all database drivers, although it is only
Dries Buytaert
committed
* useful on some of them. This method is table prefix-aware.
Dries Buytaert
committed
*
Dries Buytaert
committed
* @param $table
* The table name to use for the sequence.
* @param $field
* The field name to use for the sequence.
Dries Buytaert
committed
* @return
* A table prefix-parsed string for the sequence name.
*/
public function makeSequenceName($table, $field) {
return $this->prefixTables('{' . $table . '}_' . $field . '_seq');
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
/**
* Flatten an array of query comments into a single comment string.
*
* The comment string will be sanitized to avoid SQL injection attacks.
*
* @param $comments
* An array of query comment strings.
*
* @return
* A sanitized comment string.
*/
public function makeComment($comments) {
if (empty($comments))
return '';
// Flatten the array of comments.
$comment = implode('; ', $comments);
// Sanitize the comment string so as to avoid SQL injection attacks.
return '/* ' . $this->filterComment($comment) . ' */ ';
}
/**
* Sanitize a query comment string.
*
* Ensure a query comment does not include strings such as "* /" that might
* terminate the comment early. This avoids SQL injection attacks via the
* query comment. The comment strings in this example are separated by a
* space to avoid PHP parse errors.
*
* For example, the comment:
* @code
* db_update('example')
* ->condition('id', $id)
* ->fields(array('field2' => 10))
* ->comment('Exploit * / DROP TABLE node; --')
* ->execute()
* @endcode
*
* Would result in the following SQL statement being generated:
* @code
* "/ * Exploit * / DROP TABLE node; -- * / UPDATE example SET field2=..."
* @endcode
*
* Unless the comment is sanitised first, the SQL server would drop the
* node table and ignore the rest of the SQL statement.
*
* @param $comment
* A query comment string.
*
* @return
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
}
Dries Buytaert
committed
/**
* Executes a query string against the database.
*
* This method provides a central handler for the actual execution of every
* query. All queries executed by Drupal are executed as PDO prepared
* statements.
Dries Buytaert
committed
*
Dries Buytaert
committed
* @param $query
Dries Buytaert
committed
* The query to execute. In most cases this will be a string containing
* an SQL query with placeholders. An already-prepared instance of
* DatabaseStatementInterface may also be passed in order to allow calling
* code to manually bind variables to a query. If a
* DatabaseStatementInterface is passed, the $args array will be ignored.
Dries Buytaert
committed
* It is extremely rare that module code will need to pass a statement
Dries Buytaert
committed
* object to this method. It is used primarily for database drivers for
Dries Buytaert
committed
* databases that require special LOB field handling.
* @param $args
Dries Buytaert
committed
* An array of arguments for the prepared statement. If the prepared
Dries Buytaert
committed
* statement uses ? placeholders, this array must be an indexed array.
* If it contains named placeholders, it must be an associative array.
* @param $options
Dries Buytaert
committed
* An associative array of options to control how the query is run. See
Dries Buytaert
committed
* the documentation for DatabaseConnection::defaultOptions() for details.
Dries Buytaert
committed
* @return DatabaseStatementInterface
* This method will return one of: the executed statement, the number of
Dries Buytaert
committed
* rows affected by the query (not the number matched), or the generated
* insert IT of the last query, depending on the value of
* $options['return']. Typically that value will be set by default or a
* query builder and should not be set by a user. If there is an error,
* this method will return NULL and may throw an exception if
* $options['throw_exception'] is TRUE.
Angie Byron
committed
*
* @throws PDOException
Dries Buytaert
committed
*/
public function query($query, array $args = array(), $options = array()) {
Dries Buytaert
committed
// Use default values if not already set.
$options += $this->defaultOptions();
Dries Buytaert
committed
Dries Buytaert
committed
try {
// We allow either a pre-bound statement object or a literal string.
// In either case, we want to end up with an executed statement object,
// which we pass to PDOStatement::execute.
Dries Buytaert
committed
if ($query instanceof DatabaseStatementInterface) {
Dries Buytaert
committed
$stmt = $query;
$stmt->execute(NULL, $options);
}
else {
Angie Byron
committed
$this->expandArguments($query, $args);
$stmt = $this->prepareQuery($query);
Dries Buytaert
committed
$stmt->execute($args, $options);
}
Dries Buytaert
committed
Dries Buytaert
committed
// Depending on the type of query we may need to return a different value.
// See DatabaseConnection::defaultOptions() for a description of each
// value.
Dries Buytaert
committed
switch ($options['return']) {
case Database::RETURN_STATEMENT:
return $stmt;
case Database::RETURN_AFFECTED:
return $stmt->rowCount();
case Database::RETURN_INSERT_ID:
return $this->lastInsertId();
case Database::RETURN_NULL:
return;
default:
throw new PDOException('Invalid return directive: ' . $options['return']);
}
}
catch (PDOException $e) {
if ($options['throw_exception']) {
// Add additional debug information.
Dries Buytaert
committed
if ($query instanceof DatabaseStatementInterface) {
$e->query_string = $stmt->getQueryString();
Dries Buytaert
committed
}
else {
$e->query_string = $query;
Dries Buytaert
committed
}
$e->args = $args;
throw $e;
Dries Buytaert
committed
}
return NULL;
}
}
Dries Buytaert
committed
/**
* Expands out shorthand placeholders.
Dries Buytaert
committed
*
* Drupal supports an alternate syntax for doing arrays of values. We
* therefore need to expand them out into a full, executable query string.
Dries Buytaert
committed
*
* @param $query
* The query string to modify.
* @param $args
* The arguments for the query.
Dries Buytaert
committed
* @return
* TRUE if the query was modified, FALSE otherwise.
*/
protected function expandArguments(&$query, &$args) {
$modified = FALSE;
// If the placeholder value to insert is an array, assume that we need
// to expand it out into a comma-delimited set of placeholders.
foreach (array_filter($args, 'is_array') as $key => $data) {
$new_keys = array();
foreach ($data as $i => $value) {
// This assumes that there are no other placeholders that use the same
// name. For example, if the array placeholder is defined as :example
// and there is already an :example_2 placeholder, this will generate
// a duplicate key. We do not account for that as the calling code
// is already broken if that happens.
$new_keys[$key . '_' . $i] = $value;
Dries Buytaert
committed
}
// Update the query with the new placeholders.
// preg_replace is necessary to ensure the replacement does not affect
// placeholders that start with the same exact text. For example, if the
Angie Byron
committed
// query contains the placeholders :foo and :foobar, and :foo has an
// array of values, using str_replace would affect both placeholders,
// but using the following preg_replace would only affect :foo because
// it is followed by a non-word character.
$query = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $query);
// Update the args array with the new placeholders.
unset($args[$key]);
$args += $new_keys;
$modified = TRUE;
Dries Buytaert
committed
}
return $modified;
}
/**
* Gets the driver-specific override class if any for the specified class.
*
* @param string $class
* The class for which we want the potentially driver-specific class.
Angie Byron
committed
* @param array $files
* The name of the files in which the driver-specific class can be.
* @param $use_autoload
* If TRUE, attempt to load classes using PHP's autoload capability
* as well as the manual approach here.
* @return string
* The name of the class that should be used for this driver.
*/
Angie Byron
committed
public function getDriverClass($class, array $files = array(), $use_autoload = FALSE) {
if (empty($this->driverClasses[$class])) {
Angie Byron
committed
$driver = $this->driver();
$this->driverClasses[$class] = $class . '_' . $driver;
Database::loadDriverFile($driver, $files);
if (!class_exists($this->driverClasses[$class], $use_autoload)) {
$this->driverClasses[$class] = $class;
}
}
return $this->driverClasses[$class];
}
Dries Buytaert
committed
/**
* Prepares and returns a SELECT query object.
Dries Buytaert
committed
*
* @param $table
* The base table for this query, that is, the first table in the FROM
Dries Buytaert
committed
* clause. This table will also be used as the "base" table for query_alter
Dries Buytaert
committed
* hook implementations.
* @param $alias
* The alias of the base table of this query.
* @param $options
* An array of options on the query.
Dries Buytaert
committed
* @return SelectQueryInterface
* An appropriate SelectQuery object for this database connection. Note that
* it may be a driver-specific subclass of SelectQuery, depending on the
* driver.
*
* @see SelectQuery
Dries Buytaert
committed
*/
public function select($table, $alias = NULL, array $options = array()) {
Angie Byron
committed
$class = $this->getDriverClass('SelectQuery', array('query.inc', 'select.inc'));
Dries Buytaert
committed
return new $class($table, $alias, $this, $options);
Dries Buytaert
committed
}
/**
* Prepares and returns an INSERT query object.
Dries Buytaert
committed
*
* @param $options
* An array of options on the query.
Dries Buytaert
committed
* @return InsertQuery
Dries Buytaert
committed
* A new InsertQuery object.
*
* @see InsertQuery
Dries Buytaert
committed
*/
public function insert($table, array $options = array()) {
Angie Byron
committed
$class = $this->getDriverClass('InsertQuery', array('query.inc'));
Dries Buytaert
committed
return new $class($this, $table, $options);
Dries Buytaert
committed
}
/**
* Prepares and returns a MERGE query object.
Dries Buytaert
committed
*
* @param $options
* An array of options on the query.
Dries Buytaert
committed
* @return MergeQuery
Dries Buytaert
committed
* A new MergeQuery object.
*
* @see MergeQuery
Dries Buytaert
committed
*/
public function merge($table, array $options = array()) {
Angie Byron
committed
$class = $this->getDriverClass('MergeQuery', array('query.inc'));
Dries Buytaert
committed
return new $class($this, $table, $options);
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
* Prepares and returns an UPDATE query object.
Dries Buytaert
committed
*
* @param $options
* An array of options on the query.
Dries Buytaert
committed
* @return UpdateQuery
Dries Buytaert
committed
* A new UpdateQuery object.
*
* @see UpdateQuery
Dries Buytaert
committed
*/
public function update($table, array $options = array()) {
Angie Byron
committed
$class = $this->getDriverClass('UpdateQuery', array('query.inc'));
Dries Buytaert
committed
return new $class($this, $table, $options);
Dries Buytaert
committed
}
/**
* Prepares and returns a DELETE query object.
Dries Buytaert
committed
*
* @param $options
* An array of options on the query.
Dries Buytaert
committed
* @return DeleteQuery
Dries Buytaert
committed
* A new DeleteQuery object.
*
* @see DeleteQuery
Dries Buytaert
committed
*/
public function delete($table, array $options = array()) {
Angie Byron
committed
$class = $this->getDriverClass('DeleteQuery', array('query.inc'));
Dries Buytaert
committed
return new $class($this, $table, $options);
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Prepares and returns a TRUNCATE query object.
Dries Buytaert
committed
*
* @param $options
* An array of options on the query.
Dries Buytaert
committed
* @return TruncateQuery
* A new TruncateQuery object.
*
* @see TruncateQuery
Dries Buytaert
committed
*/
public function truncate($table, array $options = array()) {
Angie Byron
committed
$class = $this->getDriverClass('TruncateQuery', array('query.inc'));
Dries Buytaert
committed
return new $class($this, $table, $options);
}
Dries Buytaert
committed
/**
* Returns a DatabaseSchema object for manipulating the schema.
Dries Buytaert
committed
*
* This method will lazy-load the appropriate schema library file.
*
Dries Buytaert
committed
* @return DatabaseSchema
Dries Buytaert
committed
* The DatabaseSchema object for this connection.
*/
public function schema() {
Dries Buytaert
committed
if (empty($this->schema)) {
Angie Byron
committed
$class = $this->getDriverClass('DatabaseSchema', array('schema.inc'));
Dries Buytaert
committed
if (class_exists($class)) {
$this->schema = new $class($this);
}
Dries Buytaert
committed
}
Dries Buytaert
committed
return $this->schema;
Dries Buytaert
committed
}
/**
* Escapes a table name string.
*
* Force all table names to be strictly alphanumeric-plus-underscore.
* For some database drivers, it may also wrap the table name in
* database-specific escape characters.
*
* @return
* The sanitized table name string.
*/
public function escapeTable($table) {
Dries Buytaert
committed
return preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
* Escapes a field name string.
*
* Force all field names to be strictly alphanumeric-plus-underscore.
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @return
* The sanitized field name string.
*/
public function escapeField($field) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
}
Angie Byron
committed
/**
* Escapes an alias name string.
*
* Force all alias names to be strictly alphanumeric-plus-underscore. In
* contrast to DatabaseConnection::escapeField() /
Angie Byron
committed
* DatabaseConnection::escapeTable(), this doesn't allow the period (".")
* because that is not allowed in aliases.
Angie Byron
committed
*
* @return
* The sanitized field name string.
*/
public function escapeAlias($field) {
return preg_replace('/[^A-Za-z0-9_]+/', '', $field);
}
Angie Byron
committed
/**
* Escapes characters that work as wildcard characters in a LIKE pattern.
Angie Byron
committed
*
* The wildcard characters "%" and "_" as well as backslash are prefixed with
* a backslash. Use this to do a search for a verbatim string without any
Angie Byron
committed
* wildcard behavior.
*
* For example, the following does a case-insensitive query for all rows whose
* name starts with $prefix:
* @code
* $result = db_query(
* 'SELECT * FROM person WHERE name LIKE :pattern',
* array(':pattern' => db_like($prefix) . '%')
* );
* @endcode
*
* Backslash is defined as escape character for LIKE patterns in
* DatabaseCondition::mapConditionOperator().
*
* @param $string
* The string to escape.
Angie Byron
committed
* @return
* The escaped string.
*/
public function escapeLike($string) {
return addcslashes($string, '\%_');
}
/**
* Determines if there is an active transaction open.
*
* @return
* TRUE if we're currently in a transaction, FALSE otherwise.
*/
public function inTransaction() {
Dries Buytaert
committed
return ($this->transactionDepth() > 0);
}
/**
* Determines current transaction depth.
*/
public function transactionDepth() {
return count($this->transactionLayers);
}
Dries Buytaert
committed
/**
* Returns a new DatabaseTransaction object on this connection.
*
Dries Buytaert
committed
* @param $name
* Optional name of the savepoint.
*
Dries Buytaert
committed
* @see DatabaseTransaction
*/
Dries Buytaert
committed
public function startTransaction($name = '') {
$class = $this->getDriverClass('DatabaseTransaction');
return new $class($this, $name);
Dries Buytaert
committed
}
/**
Dries Buytaert
committed
* Rolls back the transaction entirely or to a named savepoint.