=== removed file 'includes/database.mysql-common.inc' --- includes/database.mysql-common.inc 2008-02-08 03:24:05 +0000 +++ includes/database.mysql-common.inc 1970-01-01 00:00:00 +0000 @@ -1,533 +0,0 @@ - $field) { - $sql .= _db_create_field_sql($field_name, _db_process_field($field)) .", \n"; - } - - // Process keys & indexes. - $keys = _db_create_keys_sql($table); - if (count($keys)) { - $sql .= implode(", \n", $keys) .", \n"; - } - - // Remove the last comma and space. - $sql = substr($sql, 0, -3) ."\n) "; - - $sql .= $table['mysql_suffix']; - - return array($sql); -} - -function _db_create_keys_sql($spec) { - $keys = array(); - - if (!empty($spec['primary key'])) { - $keys[] = 'PRIMARY KEY ('. _db_create_key_sql($spec['primary key']) .')'; - } - if (!empty($spec['unique keys'])) { - foreach ($spec['unique keys'] as $key => $fields) { - $keys[] = 'UNIQUE KEY '. $key .' ('. _db_create_key_sql($fields) .')'; - } - } - if (!empty($spec['indexes'])) { - foreach ($spec['indexes'] as $index => $fields) { - $keys[] = 'INDEX '. $index .' ('. _db_create_key_sql($fields) .')'; - } - } - - return $keys; -} - -function _db_create_key_sql($fields) { - $ret = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $ret[] = $field[0] .'('. $field[1] .')'; - } - else { - $ret[] = $field; - } - } - return implode(', ', $ret); -} - -/** - * Set database-engine specific properties for a field. - * - * @param $field - * A field description array, as specified in the schema documentation. - */ -function _db_process_field($field) { - - if (!isset($field['size'])) { - $field['size'] = 'normal'; - } - - // Set the correct database-engine specific datatype. - if (!isset($field['mysql_type'])) { - $map = db_type_map(); - $field['mysql_type'] = $map[$field['type'] .':'. $field['size']]; - } - - if ($field['type'] == 'serial') { - $field['auto_increment'] = TRUE; - } - - return $field; -} - -/** - * Create an SQL string for a field to be used in table creation or alteration. - * - * Before passing a field out of a schema definition into this function it has - * to be processed by _db_process_field(). - * - * @param $name - * Name of the field. - * @param $spec - * The field specification, as per the schema data structure format. - */ -function _db_create_field_sql($name, $spec) { - $sql = "`". $name ."` ". $spec['mysql_type']; - - if (isset($spec['length'])) { - $sql .= '('. $spec['length'] .')'; - } - elseif (isset($spec['precision']) && isset($spec['scale'])) { - $sql .= '('. $spec['precision'] .', '. $spec['scale'] .')'; - } - - if (!empty($spec['unsigned'])) { - $sql .= ' unsigned'; - } - - if (!empty($spec['not null'])) { - $sql .= ' NOT NULL'; - } - - if (!empty($spec['auto_increment'])) { - $sql .= ' auto_increment'; - } - - if (isset($spec['default'])) { - if (is_string($spec['default'])) { - $spec['default'] = "'". $spec['default'] ."'"; - } - $sql .= ' DEFAULT '. $spec['default']; - } - - if (empty($spec['not null']) && !isset($spec['default'])) { - $sql .= ' DEFAULT NULL'; - } - - return $sql; -} - -/** - * This maps a generic data type in combination with its data size - * to the engine-specific data type. - */ -function db_type_map() { - // Put :normal last so it gets preserved by array_flip. This makes - // it much easier for modules (such as schema.module) to map - // database types back into schema types. - $map = array( - 'varchar:normal' => 'VARCHAR', - 'char:normal' => 'CHAR', - - 'text:tiny' => 'TINYTEXT', - 'text:small' => 'TINYTEXT', - 'text:medium' => 'MEDIUMTEXT', - 'text:big' => 'LONGTEXT', - 'text:normal' => 'TEXT', - - 'serial:tiny' => 'TINYINT', - 'serial:small' => 'SMALLINT', - 'serial:medium' => 'MEDIUMINT', - 'serial:big' => 'BIGINT', - 'serial:normal' => 'INT', - - 'int:tiny' => 'TINYINT', - 'int:small' => 'SMALLINT', - 'int:medium' => 'MEDIUMINT', - 'int:big' => 'BIGINT', - 'int:normal' => 'INT', - - 'float:tiny' => 'FLOAT', - 'float:small' => 'FLOAT', - 'float:medium' => 'FLOAT', - 'float:big' => 'DOUBLE', - 'float:normal' => 'FLOAT', - - 'numeric:normal' => 'DECIMAL', - - 'blob:big' => 'LONGBLOB', - 'blob:normal' => 'BLOB', - - 'datetime:normal' => 'DATETIME', - ); - return $map; -} - -/** - * Rename a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be renamed. - * @param $new_name - * The new name for the table. - */ -function db_rename_table(&$ret, $table, $new_name) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME TO {'. $new_name .'}'); -} - -/** - * Drop a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be dropped. - */ -function db_drop_table(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {'. $table .'}'); -} - -/** - * Add a new field to a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * Name of the table to be altered. - * @param $field - * Name of the field to be added. - * @param $spec - * The field specification array, as taken from a schema definition. - * The specification may also contain the key 'initial', the newly - * created field will be set to the value of the key in all rows. - * This is most useful for creating NOT NULL columns with no default - * value in existing tables. - * @param $keys_new - * Optional keys and indexes specification to be created on the - * table along with adding the field. The format is the same as a - * table specification but without the 'fields' element. If you are - * adding a type 'serial' field, you MUST specify at least one key - * or index including it in this array. @see db_change_field for more - * explanation why. - */ -function db_add_field(&$ret, $table, $field, $spec, $keys_new = array()) { - $fixnull = FALSE; - if (!empty($spec['not null']) && !isset($spec['default'])) { - $fixnull = TRUE; - $spec['not null'] = FALSE; - } - $query = 'ALTER TABLE {'. $table .'} ADD '; - $query .= _db_create_field_sql($field, _db_process_field($spec)); - if (count($keys_new)) { - $query .= ', ADD '. implode(', ADD ', _db_create_keys_sql($keys_new)); - } - $ret[] = update_sql($query); - if (isset($spec['initial'])) { - // All this because update_sql does not support %-placeholders. - $sql = 'UPDATE {'. $table .'} SET '. $field .' = '. db_type_placeholder($spec['type']); - $result = db_query($sql, $spec['initial']); - $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql .' ('. $spec['initial'] .')')); - } - if ($fixnull) { - $spec['not null'] = TRUE; - db_change_field($ret, $table, $field, $field, $spec); - } -} - -/** - * Drop a field. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $field - * The field to be dropped. - */ -function db_drop_field(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP '. $field); -} - -/** - * Set the default value for a field. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - * @param $default - * Default value to be set. NULL for 'default NULL'. - */ -function db_field_set_default(&$ret, $table, $field, $default) { - if ($default == NULL) { - $default = 'NULL'; - } - else { - $default = is_string($default) ? "'$default'" : $default; - } - - $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default); -} - -/** - * Set a field to have no default value. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - */ -function db_field_set_no_default(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT'); -} - -/** - * Add a primary key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $fields - * Fields for the primary key. - */ -function db_add_primary_key(&$ret, $table, $fields) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('. - _db_create_key_sql($fields) .')'); -} - -/** - * Drop the primary key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - */ -function db_drop_primary_key(&$ret, $table) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP PRIMARY KEY'); -} - -/** - * Add a unique key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - * @param $fields - * An array of field names. - */ -function db_add_unique_key(&$ret, $table, $name, $fields) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD UNIQUE KEY '. - $name .' ('. _db_create_key_sql($fields) .')'); -} - -/** - * Drop a unique key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - */ -function db_drop_unique_key(&$ret, $table, $name) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP KEY '. $name); -} - -/** - * Add an index. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - * @param $fields - * An array of field names. - */ -function db_add_index(&$ret, $table, $name, $fields) { - $query = 'ALTER TABLE {'. $table .'} ADD INDEX '. $name .' ('. _db_create_key_sql($fields) .')'; - $ret[] = update_sql($query); -} - -/** - * Drop an index. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - */ -function db_drop_index(&$ret, $table, $name) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP INDEX '. $name); -} - -/** - * Change a field definition. - * - * IMPORTANT NOTE: To maintain database portability, you have to explicitly - * recreate all indices and primary keys that are using the changed field. - * - * That means that you have to drop all affected keys and indexes with - * db_drop_{primary_key,unique_key,index}() before calling db_change_field(). - * To recreate the keys and indices, pass the key definitions as the - * optional $keys_new argument directly to db_change_field(). - * - * For example, suppose you have: - * @code - * $schema['foo'] = array( - * 'fields' => array( - * 'bar' => array('type' => 'int', 'not null' => TRUE) - * ), - * 'primary key' => array('bar') - * ); - * @endcode - * and you want to change foo.bar to be type serial, leaving it as the - * primary key. The correct sequence is: - * @code - * db_drop_primary_key($ret, 'foo'); - * db_change_field($ret, 'foo', 'bar', 'bar', - * array('type' => 'serial', 'not null' => TRUE), - * array('primary key' => array('bar'))); - * @endcode - * - * The reasons for this are due to the different database engines: - * - * On PostgreSQL, changing a field definition involves adding a new field - * and dropping an old one which* causes any indices, primary keys and - * sequences (from serial-type fields) that use the changed field to be dropped. - * - * On MySQL, all type 'serial' fields must be part of at least one key - * or index as soon as they are created. You cannot use - * db_add_{primary_key,unique_key,index}() for this purpose because - * the ALTER TABLE command will fail to add the column without a key - * or index specification. The solution is to use the optional - * $keys_new argument to create the key or index at the same time as - * field. - * - * You could use db_add_{primary_key,unique_key,index}() in all cases - * unless you are converting a field to be type serial. You can use - * the $keys_new argument in all cases. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * Name of the table. - * @param $field - * Name of the field to change. - * @param $field_new - * New name for the field (set to the same as $field if you don't want to change the name). - * @param $spec - * The field specification for the new field. - * @param $keys_new - * Optional keys and indexes specification to be created on the - * table along with changing the field. The format is the same as a - * table specification but without the 'fields' element. - */ - -function db_change_field(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) { - $sql = 'ALTER TABLE {'. $table .'} CHANGE '. $field .' '. - _db_create_field_sql($field_new, _db_process_field($spec)); - if (count($keys_new)) { - $sql .= ', ADD '. implode(', ADD ', _db_create_keys_sql($keys_new)); - } - $ret[] = update_sql($sql); -} - -/** - * Returns the last insert id. - * - * @param $table - * The name of the table you inserted into. - * @param $field - * The name of the autoincrement field. - */ -function db_last_insert_id($table, $field) { - return db_result(db_query('SELECT LAST_INSERT_ID()')); -} === removed file 'includes/database.mysqli.inc' --- includes/database.mysqli.inc 2008-02-21 20:12:47 +0000 +++ includes/database.mysqli.inc 1970-01-01 00:00:00 +0000 @@ -1,376 +0,0 @@ - $t('MySQL database'), - 'value' => ($phase == 'runtime') ? l($version, 'admin/reports/status/sql') : $version, - ); - - if (version_compare($version, DRUPAL_MINIMUM_MYSQL) < 0) { - $form['mysql']['severity'] = REQUIREMENT_ERROR; - $form['mysql']['description'] = $t('Your MySQL Server is too old. Drupal requires at least MySQL %version.', array('%version' => DRUPAL_MINIMUM_MYSQL)); - } - - return $form; -} - -/** - * Returns the version of the database server currently in use. - * - * @return Database server version - */ -function db_version() { - global $active_db; - list($version) = explode('-', mysqli_get_server_info($active_db)); - return $version; -} - -/** - * Initialise a database connection. - * - * Note that mysqli does not support persistent connections. - */ -function db_connect($url) { - // Check if MySQLi support is present in PHP - if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) { - _db_error_page('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your php.ini to see how you can enable it.'); - } - - $url = parse_url($url); - - // Decode url-encoded information in the db connection string - $url['user'] = urldecode($url['user']); - // Test if database url has a password. - $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : ''; - $url['host'] = urldecode($url['host']); - $url['path'] = urldecode($url['path']); - if (!isset($url['port'])) { - $url['port'] = NULL; - } - - $connection = mysqli_init(); - @mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS); - - if (mysqli_connect_errno() > 0) { - _db_error_page(mysqli_connect_error()); - } - - // Force UTF-8. - mysqli_query($connection, 'SET NAMES "utf8"'); - // Require ANSI mode to improve SQL portability. - mysqli_query($connection, "SET SESSION sql_mode='ANSI'"); - - return $connection; -} - -/** - * Helper function for db_query(). - */ -function _db_query($query, $debug = 0) { - global $active_db, $queries, $user; - - if (variable_get('dev_query', 0)) { - list($usec, $sec) = explode(' ', microtime()); - $timer = (float)$usec + (float)$sec; - // If devel.module query logging is enabled, prepend a comment with the username and calling function - // to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact - // code is issueing the slow query. - $bt = debug_backtrace(); - // t() may not be available yet so we don't wrap 'Anonymous' - $name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous'); - // str_replace() to prevent SQL injection via username or anonymous name. - $name = str_replace(array('*', '/'), '', $name); - $query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query; - } - - $result = mysqli_query($active_db, $query); - - if (variable_get('dev_query', 0)) { - $query = $bt[2]['function'] ."\n". $query; - list($usec, $sec) = explode(' ', microtime()); - $stop = (float)$usec + (float)$sec; - $diff = $stop - $timer; - $queries[] = array($query, $diff); - } - - if ($debug) { - print '

query: '. $query .'
error:'. mysqli_error($active_db) .'

'; - } - - if (!mysqli_errno($active_db)) { - return $result; - } - else { - // Indicate to drupal_error_handler that this is a database error. - ${DB_ERROR} = TRUE; - trigger_error(check_plain(mysqli_error($active_db) ."\nquery: ". $query), E_USER_WARNING); - return FALSE; - } -} - -/** - * Fetch one result row from the previous query as an object. - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * An object representing the next row of the result, or FALSE. The attributes - * of this object are the table fields selected by the query. - */ -function db_fetch_object($result) { - if ($result) { - $object = mysqli_fetch_object($result); - return isset($object) ? $object : FALSE; - } -} - -/** - * Fetch one result row from the previous query as an array. - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * An associative array representing the next row of the result, or FALSE. - * The keys of this object are the names of the table fields selected by the - * query, and the values are the field values for this result row. - */ -function db_fetch_array($result) { - if ($result) { - $array = mysqli_fetch_array($result, MYSQLI_ASSOC); - return isset($array) ? $array : FALSE; - } -} - -/** - * Return an individual result field from the previous query. - * - * Only use this function if exactly one field is being selected; otherwise, - * use db_fetch_object() or db_fetch_array(). - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * The resulting field or FALSE. - */ -function db_result($result) { - if ($result && mysqli_num_rows($result) > 0) { - // The mysqli_fetch_row function has an optional second parameter $row - // but that can't be used for compatibility with Oracle, DB2, etc. - $array = mysqli_fetch_row($result); - return $array[0]; - } - return FALSE; -} - -/** - * Determine whether the previous query caused an error. - */ -function db_error() { - global $active_db; - return mysqli_errno($active_db); -} - -/** - * Determine the number of rows changed by the preceding query. - */ -function db_affected_rows() { - global $active_db; /* mysqli connection resource */ - return mysqli_affected_rows($active_db); -} - -/** - * Runs a limited-range query in the active database. - * - * Use this as a substitute for db_query() when a subset of the query is to be - * returned. - * User-supplied arguments to the query should be passed in as separate parameters - * so that they can be properly escaped to avoid SQL injection attacks. - * - * @param $query - * A string containing an SQL query. - * @param ... - * A variable number of arguments which are substituted into the query - * using printf() syntax. The query arguments can be enclosed in one - * array instead. - * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose - * in '') and %%. - * - * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, - * and TRUE values to decimal 1. - * - * @param $from - * The first result row to return. - * @param $count - * The maximum number of result rows to return. - * @return - * A database query result resource, or FALSE if the query was not executed - * correctly. - */ -function db_query_range($query) { - $args = func_get_args(); - $count = array_pop($args); - $from = array_pop($args); - array_shift($args); - - $query = db_prefix_tables($query); - if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax - $args = $args[0]; - } - _db_query_callback($args, TRUE); - $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - $query .= ' LIMIT '. (int)$from .', '. (int)$count; - return _db_query($query); -} - -/** - * Runs a SELECT query and stores its results in a temporary table. - * - * Use this as a substitute for db_query() when the results need to stored - * in a temporary table. Temporary tables exist for the duration of the page - * request. - * User-supplied arguments to the query should be passed in as separate parameters - * so that they can be properly escaped to avoid SQL injection attacks. - * - * Note that if you need to know how many results were returned, you should do - * a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does - * not give consistent result across different database types in this case. - * - * @param $query - * A string containing a normal SELECT SQL query. - * @param ... - * A variable number of arguments which are substituted into the query - * using printf() syntax. The query arguments can be enclosed in one - * array instead. - * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose - * in '') and %%. - * - * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, - * and TRUE values to decimal 1. - * - * @param $table - * The name of the temporary table to select into. This name will not be - * prefixed as there is no risk of collision. - * @return - * A database query result resource, or FALSE if the query was not executed - * correctly. - */ -function db_query_temporary($query) { - $args = func_get_args(); - $tablename = array_pop($args); - array_shift($args); - - $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' Engine=HEAP SELECT', db_prefix_tables($query)); - if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax - $args = $args[0]; - } - _db_query_callback($args, TRUE); - $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - return _db_query($query); -} - -/** - * Returns a properly formatted Binary Large Object value. - * - * @param $data - * Data to encode. - * @return - * Encoded data. - */ -function db_encode_blob($data) { - global $active_db; - return "'". mysqli_real_escape_string($active_db, $data) ."'"; -} - -/** - * Returns text from a Binary Large OBject value. - * - * @param $data - * Data to decode. - * @return - * Decoded data. - */ -function db_decode_blob($data) { - return $data; -} - -/** - * Prepare user input for use in a database query, preventing SQL injection attacks. - */ -function db_escape_string($text) { - global $active_db; - return mysqli_real_escape_string($active_db, $text); -} - -/** - * Lock a table. - */ -function db_lock_table($table) { - db_query('LOCK TABLES {'. db_escape_table($table) .'} WRITE'); -} - -/** - * Unlock all locked tables. - */ -function db_unlock_tables() { - db_query('UNLOCK TABLES'); -} - -/** - * Check if a table exists. - */ -function db_table_exists($table) { - return (bool) db_fetch_object(db_query("SHOW TABLES LIKE '{". db_escape_table($table) ."}'")); -} - -/** - * Check if a column exists in the given table. - */ -function db_column_exists($table, $column) { - return (bool) db_fetch_object(db_query("SHOW COLUMNS FROM {". db_escape_table($table) ."} LIKE '". db_escape_table($column) ."'")); -} - -/** - * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to - * the SELECT list entry of the given query and the resulting query is returned. - * This function only applies the wrapper if a DISTINCT doesn't already exist in - * the query. - * - * @param $table Table containing the field to set as DISTINCT - * @param $field Field to set as DISTINCT - * @param $query Query to apply the wrapper to - * @return SQL query with the DISTINCT wrapper surrounding the given table.field. - */ -function db_distinct_field($table, $field, $query) { - $field_to_select = 'DISTINCT('. $table .'.'. $field .')'; - // (? $t('PostgreSQL database'), - 'value' => $version, - ); - - if (version_compare($version, DRUPAL_MINIMUM_PGSQL) < 0) { - $form['pgsql']['severity'] = REQUIREMENT_ERROR; - $form['pgsql']['description'] = $t('Your PostgreSQL Server is too old. Drupal requires at least PostgreSQL %version.', array('%version' => DRUPAL_MINIMUM_PGSQL)); - } - - return $form; -} - -/** - * Returns the version of the database server currently in use. - * - * @return Database server version - */ -function db_version() { - return db_result(db_query("SHOW SERVER_VERSION")); -} - -/** - * Initialize a database connection. - */ -function db_connect($url) { - // Check if PostgreSQL support is present in PHP - if (!function_exists('pg_connect')) { - _db_error_page('Unable to use the PostgreSQL database because the PostgreSQL extension for PHP is not installed. Check your php.ini to see how you can enable it.'); - } - - $url = parse_url($url); - $conn_string = ''; - - // Decode url-encoded information in the db connection string - if (isset($url['user'])) { - $conn_string .= ' user='. urldecode($url['user']); - } - if (isset($url['pass'])) { - $conn_string .= ' password='. urldecode($url['pass']); - } - if (isset($url['host'])) { - $conn_string .= ' host='. urldecode($url['host']); - } - if (isset($url['path'])) { - $conn_string .= ' dbname='. substr(urldecode($url['path']), 1); - } - if (isset($url['port'])) { - $conn_string .= ' port='. urldecode($url['port']); - } - - // pg_last_error() does not return a useful error message for database - // connection errors. We must turn on error tracking to get at a good error - // message, which will be stored in $php_errormsg. - $track_errors_previous = ini_get('track_errors'); - ini_set('track_errors', 1); - - $connection = @pg_connect($conn_string); - if (!$connection) { - require_once './includes/unicode.inc'; - _db_error_page(decode_entities($php_errormsg)); - } - - // Restore error tracking setting - ini_set('track_errors', $track_errors_previous); - - return $connection; -} - -/** - * Runs a basic query in the active database. - * - * User-supplied arguments to the query should be passed in as separate - * parameters so that they can be properly escaped to avoid SQL injection - * attacks. - * - * @param $query - * A string containing an SQL query. - * @param ... - * A variable number of arguments which are substituted into the query - * using printf() syntax. Instead of a variable number of query arguments, - * you may also pass a single array containing the query arguments. - * - * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose - * in '') and %%. - * - * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, - * and TRUE values to decimal 1. - * - * @return - * A database query result resource, or FALSE if the query was not - * executed correctly. - */ -function db_query($query) { - $args = func_get_args(); - array_shift($args); - $query = db_prefix_tables($query); - if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax - $args = $args[0]; - } - _db_query_callback($args, TRUE); - $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - return _db_query($query); -} - -/** - * Helper function for db_query(). - */ -function _db_query($query, $debug = 0) { - global $active_db, $last_result, $queries; - - if (variable_get('dev_query', 0)) { - list($usec, $sec) = explode(' ', microtime()); - $timer = (float)$usec + (float)$sec; - } - - $last_result = pg_query($active_db, $query); - - if (variable_get('dev_query', 0)) { - $bt = debug_backtrace(); - $query = $bt[2]['function'] ."\n". $query; - list($usec, $sec) = explode(' ', microtime()); - $stop = (float)$usec + (float)$sec; - $diff = $stop - $timer; - $queries[] = array($query, $diff); - } - - if ($debug) { - print '

query: '. $query .'
error:'. pg_last_error($active_db) .'

'; - } - - if ($last_result !== FALSE) { - return $last_result; - } - else { - // Indicate to drupal_error_handler that this is a database error. - ${DB_ERROR} = TRUE; - trigger_error(check_plain(pg_last_error($active_db) ."\nquery: ". $query), E_USER_WARNING); - return FALSE; - } -} - -/** - * Fetch one result row from the previous query as an object. - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * An object representing the next row of the result, or FALSE. The attributes - * of this object are the table fields selected by the query. - */ -function db_fetch_object($result) { - if ($result) { - return pg_fetch_object($result); - } -} - -/** - * Fetch one result row from the previous query as an array. - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * An associative array representing the next row of the result, or FALSE. - * The keys of this object are the names of the table fields selected by the - * query, and the values are the field values for this result row. - */ -function db_fetch_array($result) { - if ($result) { - return pg_fetch_assoc($result); - } -} - -/** - * Return an individual result field from the previous query. - * - * Only use this function if exactly one field is being selected; otherwise, - * use db_fetch_object() or db_fetch_array(). - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * The resulting field or FALSE. - */ -function db_result($result) { - if ($result && pg_num_rows($result) > 0) { - $array = pg_fetch_row($result); - return $array[0]; - } - return FALSE; -} - -/** - * Determine whether the previous query caused an error. - */ -function db_error() { - global $active_db; - return pg_last_error($active_db); -} - -/** - * Returns the last insert id. This function is thread safe. - * - * @param $table - * The name of the table you inserted into. - * @param $field - * The name of the autoincrement field. - */ -function db_last_insert_id($table, $field) { - return db_result(db_query("SELECT CURRVAL('{". db_escape_table($table) ."}_". db_escape_table($field) ."_seq')")); -} - -/** - * Determine the number of rows changed by the preceding query. - */ -function db_affected_rows() { - global $last_result; - return empty($last_result) ? 0 : pg_affected_rows($last_result); -} - -/** - * Runs a limited-range query in the active database. - * - * Use this as a substitute for db_query() when a subset of the query - * is to be returned. - * User-supplied arguments to the query should be passed in as separate - * parameters so that they can be properly escaped to avoid SQL injection - * attacks. - * - * @param $query - * A string containing an SQL query. - * @param ... - * A variable number of arguments which are substituted into the query - * using printf() syntax. Instead of a variable number of query arguments, - * you may also pass a single array containing the query arguments. - * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose - * in '') and %%. - * - * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, - * and TRUE values to decimal 1. - * - * @param $from - * The first result row to return. - * @param $count - * The maximum number of result rows to return. - * @return - * A database query result resource, or FALSE if the query was not executed - * correctly. - */ -function db_query_range($query) { - $args = func_get_args(); - $count = array_pop($args); - $from = array_pop($args); - array_shift($args); - - $query = db_prefix_tables($query); - if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax - $args = $args[0]; - } - _db_query_callback($args, TRUE); - $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - $query .= ' LIMIT '. (int)$count .' OFFSET '. (int)$from; - return _db_query($query); -} - -/** - * Runs a SELECT query and stores its results in a temporary table. - * - * Use this as a substitute for db_query() when the results need to stored - * in a temporary table. Temporary tables exist for the duration of the page - * request. - * User-supplied arguments to the query should be passed in as separate parameters - * so that they can be properly escaped to avoid SQL injection attacks. - * - * Note that if you need to know how many results were returned, you should do - * a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does - * not give consistent result across different database types in this case. - * - * @param $query - * A string containing a normal SELECT SQL query. - * @param ... - * A variable number of arguments which are substituted into the query - * using printf() syntax. The query arguments can be enclosed in one - * array instead. - * Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose - * in '') and %%. - * - * NOTE: using this syntax will cast NULL and FALSE values to decimal 0, - * and TRUE values to decimal 1. - * - * @param $table - * The name of the temporary table to select into. This name will not be - * prefixed as there is no risk of collision. - * @return - * A database query result resource, or FALSE if the query was not executed - * correctly. - */ -function db_query_temporary($query) { - $args = func_get_args(); - $tablename = array_pop($args); - array_shift($args); - - $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' AS SELECT', db_prefix_tables($query)); - if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax - $args = $args[0]; - } - _db_query_callback($args, TRUE); - $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query); - return _db_query($query); -} - -/** - * Returns a properly formatted Binary Large OBject value. - * In case of PostgreSQL encodes data for insert into bytea field. - * - * @param $data - * Data to encode. - * @return - * Encoded data. - */ -function db_encode_blob($data) { - return "'". pg_escape_bytea($data) ."'"; -} - -/** - * Returns text from a Binary Large OBject value. - * In case of PostgreSQL decodes data after select from bytea field. - * - * @param $data - * Data to decode. - * @return - * Decoded data. - */ -function db_decode_blob($data) { - return pg_unescape_bytea($data); -} - -/** - * Prepare user input for use in a database query, preventing SQL injection attacks. - * Note: This function requires PostgreSQL 7.2 or later. - */ -function db_escape_string($text) { - return pg_escape_string($text); -} - -/** - * Lock a table. - * This function automatically starts a transaction. - */ -function db_lock_table($table) { - db_query('BEGIN; LOCK TABLE {'. db_escape_table($table) .'} IN EXCLUSIVE MODE'); -} - -/** - * Unlock all locked tables. - * This function automatically commits a transaction. - */ -function db_unlock_tables() { - db_query('COMMIT'); -} - -/** - * Check if a table exists. - */ -function db_table_exists($table) { - return (bool) db_result(db_query("SELECT COUNT(*) FROM pg_class WHERE relname = '{". db_escape_table($table) ."}'")); -} - -/** - * Check if a column exists in the given table. - */ -function db_column_exists($table, $column) { - return (bool) db_result(db_query("SELECT COUNT(pg_attribute.attname) FROM pg_class, pg_attribute WHERE pg_attribute.attrelid = pg_class.oid AND pg_class.relname = '{". db_escape_table($table) ."}' AND attname = '". db_escape_table($column) ."'")); -} - -/** - * Verify if the database is set up correctly. - */ -function db_check_setup() { - $t = get_t(); - - $encoding = db_result(db_query('SHOW server_encoding')); - if (!in_array(strtolower($encoding), array('unicode', 'utf8'))) { - drupal_set_message($t('Your PostgreSQL database is set up with the wrong character encoding (%encoding). It is possible it will not work as expected. It is advised to recreate it with UTF-8/Unicode encoding. More information can be found in the PostgreSQL documentation.', array('%encoding' => $encoding, '@url' => 'http://www.postgresql.org/docs/7.4/interactive/multibyte.html')), 'status'); - } -} - -/** - * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to - * the SELECT list entry of the given query and the resulting query is returned. - * This function only applies the wrapper if a DISTINCT doesn't already exist in - * the query. - * - * @param $table Table containing the field to set as DISTINCT - * @param $field Field to set as DISTINCT - * @param $query Query to apply the wrapper to - * @return SQL query with the DISTINCT wrapper surrounding the given table.field. - */ -function db_distinct_field($table, $field, $query) { - $field_to_select = 'DISTINCT ON ('. $table .'.'. $field .") $table.$field"; - // (? 'varchar', - 'char:normal' => 'character', - - 'text:tiny' => 'text', - 'text:small' => 'text', - 'text:medium' => 'text', - 'text:big' => 'text', - 'text:normal' => 'text', - - 'int:tiny' => 'smallint', - 'int:small' => 'smallint', - 'int:medium' => 'int', - 'int:big' => 'bigint', - 'int:normal' => 'int', - - 'float:tiny' => 'real', - 'float:small' => 'real', - 'float:medium' => 'real', - 'float:big' => 'double precision', - 'float:normal' => 'real', - - 'numeric:normal' => 'numeric', - - 'blob:big' => 'bytea', - 'blob:normal' => 'bytea', - - 'datetime:normal' => 'timestamp', - - 'serial:tiny' => 'serial', - 'serial:small' => 'serial', - 'serial:medium' => 'serial', - 'serial:big' => 'bigserial', - 'serial:normal' => 'serial', - ); - return $map; -} - -/** - * Generate SQL to create a new table from a Drupal schema definition. - * - * @param $name - * The name of the table to create. - * @param $table - * A Schema API table definition array. - * @return - * An array of SQL statements to create the table. - */ -function db_create_table_sql($name, $table) { - $sql_fields = array(); - foreach ($table['fields'] as $field_name => $field) { - $sql_fields[] = _db_create_field_sql($field_name, _db_process_field($field)); - } - - $sql_keys = array(); - if (isset($table['primary key']) && is_array($table['primary key'])) { - $sql_keys[] = 'PRIMARY KEY ('. implode(', ', $table['primary key']) .')'; - } - if (isset($table['unique keys']) && is_array($table['unique keys'])) { - foreach ($table['unique keys'] as $key_name => $key) { - $sql_keys[] = 'CONSTRAINT {'. $name .'}_'. $key_name .'_key UNIQUE ('. implode(', ', $key) .')'; - } - } - - $sql = "CREATE TABLE {". $name ."} (\n\t"; - $sql .= implode(",\n\t", $sql_fields); - if (count($sql_keys) > 0) { - $sql .= ",\n\t"; - } - $sql .= implode(",\n\t", $sql_keys); - $sql .= "\n)"; - $statements[] = $sql; - - if (isset($table['indexes']) && is_array($table['indexes'])) { - foreach ($table['indexes'] as $key_name => $key) { - $statements[] = _db_create_index_sql($name, $key_name, $key); - } - } - - return $statements; -} - -function _db_create_index_sql($table, $name, $fields) { - $query = 'CREATE INDEX {'. $table .'}_'. $name .'_idx ON {'. $table .'} ('; - $query .= _db_create_key_sql($fields) .')'; - return $query; -} - -function _db_create_key_sql($fields) { - $ret = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $ret[] = 'substr('. $field[0] .', 1, '. $field[1] .')'; - } - else { - $ret[] = $field; - } - } - return implode(', ', $ret); -} - -function _db_create_keys(&$ret, $table, $new_keys) { - if (isset($new_keys['primary key'])) { - db_add_primary_key($ret, $table, $new_keys['primary key']); - } - if (isset($new_keys['unique keys'])) { - foreach ($new_keys['unique keys'] as $name => $fields) { - db_add_unique_key($ret, $table, $name, $fields); - } - } - if (isset($new_keys['indexes'])) { - foreach ($new_keys['indexes'] as $name => $fields) { - db_add_index($ret, $table, $name, $fields); - } - } -} - -/** - * Set database-engine specific properties for a field. - * - * @param $field - * A field description array, as specified in the schema documentation. - */ -function _db_process_field($field) { - if (!isset($field['size'])) { - $field['size'] = 'normal'; - } - // Set the correct database-engine specific datatype. - if (!isset($field['pgsql_type'])) { - $map = db_type_map(); - $field['pgsql_type'] = $map[$field['type'] .':'. $field['size']]; - } - if ($field['type'] == 'serial') { - unset($field['not null']); - } - return $field; -} - -/** - * Create an SQL string for a field to be used in table creation or alteration. - * - * Before passing a field out of a schema definition into this function it has - * to be processed by _db_process_field(). - * - * @param $name - * Name of the field. - * @param $spec - * The field specification, as per the schema data structure format. - */ -function _db_create_field_sql($name, $spec) { - $sql = $name .' '. $spec['pgsql_type']; - - if ($spec['type'] == 'serial') { - unset($spec['not null']); - } - if (!empty($spec['unsigned'])) { - if ($spec['type'] == 'serial') { - $sql .= " CHECK ($name >= 0)"; - } - else { - $sql .= '_unsigned'; - } - } - - if (!empty($spec['length'])) { - $sql .= '('. $spec['length'] .')'; - } - elseif (isset($spec['precision']) && isset($spec['scale'])) { - $sql .= '('. $spec['precision'] .', '. $spec['scale'] .')'; - } - - if (isset($spec['not null']) && $spec['not null']) { - $sql .= ' NOT NULL'; - } - if (isset($spec['default'])) { - $default = is_string($spec['default']) ? "'". $spec['default'] ."'" : $spec['default']; - $sql .= " default $default"; - } - - return $sql; -} - -/** - * Rename a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be renamed. - * @param $new_name - * The new name for the table. - */ -function db_rename_table(&$ret, $table, $new_name) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME TO {'. $new_name .'}'); -} - -/** - * Drop a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be dropped. - */ -function db_drop_table(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {'. $table .'}'); -} - -/** - * Add a new field to a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * Name of the table to be altered. - * @param $field - * Name of the field to be added. - * @param $spec - * The field specification array, as taken from a schema definition. - * The specification may also contain the key 'initial', the newly - * created field will be set to the value of the key in all rows. - * This is most useful for creating NOT NULL columns with no default - * value in existing tables. - * @param $keys_new - * Optional keys and indexes specification to be created on the - * table along with adding the field. The format is the same as a - * table specification but without the 'fields' element. If you are - * adding a type 'serial' field, you MUST specify at least one key - * or index including it in this array. @see db_change_field for more - * explanation why. - */ -function db_add_field(&$ret, $table, $field, $spec, $new_keys = array()) { - $fixnull = FALSE; - if (!empty($spec['not null']) && !isset($spec['default'])) { - $fixnull = TRUE; - $spec['not null'] = FALSE; - } - $query = 'ALTER TABLE {'. $table .'} ADD COLUMN '; - $query .= _db_create_field_sql($field, _db_process_field($spec)); - $ret[] = update_sql($query); - if (isset($spec['initial'])) { - // All this because update_sql does not support %-placeholders. - $sql = 'UPDATE {'. $table .'} SET '. $field .' = '. db_type_placeholder($spec['type']); - $result = db_query($sql, $spec['initial']); - $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql .' ('. $spec['initial'] .')')); - } - if ($fixnull) { - $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field SET NOT NULL"); - } - if (isset($new_keys)) { - _db_create_keys($ret, $table, $new_keys); - } -} - -/** - * Drop a field. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $field - * The field to be dropped. - */ -function db_drop_field(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP COLUMN '. $field); -} - -/** - * Set the default value for a field. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - * @param $default - * Default value to be set. NULL for 'default NULL'. - */ -function db_field_set_default(&$ret, $table, $field, $default) { - if ($default == NULL) { - $default = 'NULL'; - } - else { - $default = is_string($default) ? "'$default'" : $default; - } - - $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default); -} - -/** - * Set a field to have no default value. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - */ -function db_field_set_no_default(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT'); -} - -/** - * Add a primary key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $fields - * Fields for the primary key. - */ -function db_add_primary_key(&$ret, $table, $fields) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('. - implode(',', $fields) .')'); -} - -/** - * Drop the primary key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - */ -function db_drop_primary_key(&$ret, $table) { - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT {'. $table .'}_pkey'); -} - -/** - * Add a unique key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - * @param $fields - * An array of field names. - */ -function db_add_unique_key(&$ret, $table, $name, $fields) { - $name = '{'. $table .'}_'. $name .'_key'; - $ret[] = update_sql('ALTER TABLE {'. $table .'} ADD CONSTRAINT '. - $name .' UNIQUE ('. implode(',', $fields) .')'); -} - -/** - * Drop a unique key. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - */ -function db_drop_unique_key(&$ret, $table, $name) { - $name = '{'. $table .'}_'. $name .'_key'; - $ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT '. $name); -} - -/** - * Add an index. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - * @param $fields - * An array of field names. - */ -function db_add_index(&$ret, $table, $name, $fields) { - $ret[] = update_sql(_db_create_index_sql($table, $name, $fields)); -} - -/** - * Drop an index. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - */ -function db_drop_index(&$ret, $table, $name) { - $name = '{'. $table .'}_'. $name .'_idx'; - $ret[] = update_sql('DROP INDEX '. $name); -} - -/** - * Change a field definition. - * - * IMPORTANT NOTE: To maintain database portability, you have to explicitly - * recreate all indices and primary keys that are using the changed field. - * - * That means that you have to drop all affected keys and indexes with - * db_drop_{primary_key,unique_key,index}() before calling db_change_field(). - * To recreate the keys and indices, pass the key definitions as the - * optional $new_keys argument directly to db_change_field(). - * - * For example, suppose you have: - * @code - * $schema['foo'] = array( - * 'fields' => array( - * 'bar' => array('type' => 'int', 'not null' => TRUE) - * ), - * 'primary key' => array('bar') - * ); - * @endcode - * and you want to change foo.bar to be type serial, leaving it as the - * primary key. The correct sequence is: - * @code - * db_drop_primary_key($ret, 'foo'); - * db_change_field($ret, 'foo', 'bar', 'bar', - * array('type' => 'serial', 'not null' => TRUE), - * array('primary key' => array('bar'))); - * @endcode - * - * The reasons for this are due to the different database engines: - * - * On PostgreSQL, changing a field definition involves adding a new field - * and dropping an old one which* causes any indices, primary keys and - * sequences (from serial-type fields) that use the changed field to be dropped. - * - * On MySQL, all type 'serial' fields must be part of at least one key - * or index as soon as they are created. You cannot use - * db_add_{primary_key,unique_key,index}() for this purpose because - * the ALTER TABLE command will fail to add the column without a key - * or index specification. The solution is to use the optional - * $new_keys argument to create the key or index at the same time as - * field. - * - * You could use db_add_{primary_key,unique_key,index}() in all cases - * unless you are converting a field to be type serial. You can use - * the $new_keys argument in all cases. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * Name of the table. - * @param $field - * Name of the field to change. - * @param $field_new - * New name for the field (set to the same as $field if you don't want to change the name). - * @param $spec - * The field specification for the new field. - * @param $new_keys - * Optional keys and indexes specification to be created on the - * table along with changing the field. The format is the same as a - * table specification but without the 'fields' element. - */ -function db_change_field(&$ret, $table, $field, $field_new, $spec, $new_keys = array()) { - $ret[] = update_sql("ALTER TABLE {". $table ."} RENAME $field TO ". $field ."_old"); - $not_null = isset($spec['not null']) ? $spec['not null'] : FALSE; - unset($spec['not null']); - - db_add_field($ret, $table, "$field_new", $spec); - - $ret[] = update_sql("UPDATE {". $table ."} SET $field_new = ". $field ."_old"); - - if ($not_null) { - $ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field_new SET NOT NULL"); - } - - db_drop_field($ret, $table, $field .'_old'); - - if (isset($new_keys)) { - _db_create_keys($ret, $table, $new_keys); - } -} - -/** - * @} End of "ingroup schemaapi". - */ - === removed file 'includes/install.mysql.inc' --- includes/install.mysql.inc 2008-01-23 09:59:28 +0000 +++ includes/install.mysql.inc 1970-01-01 00:00:00 +0000 @@ -1,117 +0,0 @@ -
  • Are you sure you have the correct username and password?
  • Are you sure that you have typed the correct database hostname?
  • Are you sure that the database server is running?
  • For more help, see the Installation and upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysql_error())), 'error'); - return FALSE; - } - - // Test selecting the database. - if (!mysql_select_db(substr($url['path'], 1))) { - drupal_set_message(st('Failed to select your database on your MySQL database server, which means the connection username and password are valid, but there is a problem accessing your data. MySQL reports the following message: %error.For more help, see the Installation and upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysql_error())), 'error'); - return FALSE; - } - - $success = array('CONNECT'); - - // Test CREATE. - $query = 'CREATE TABLE drupal_install_test (id int NULL)'; - $result = mysql_query($query); - if ($error = mysql_error()) { - drupal_set_message(st('Failed to create a test table on your MySQL database server with the command %query. MySQL reports the following message: %error.For more help, see the Installation and upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error'); - return FALSE; - } - $err = FALSE; - $success[] = 'SELECT'; - $success[] = 'CREATE'; - - // Test INSERT. - $query = 'INSERT INTO drupal_install_test (id) VALUES (1)'; - $result = mysql_query($query); - if ($error = mysql_error()) { - drupal_set_message(st('Failed to insert a value into a test table on your MySQL database server. We tried inserting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error'); - $err = TRUE; - } - else { - $success[] = 'INSERT'; - } - - // Test UPDATE. - $query = 'UPDATE drupal_install_test SET id = 2'; - $result = mysql_query($query); - if ($error = mysql_error()) { - drupal_set_message(st('Failed to update a value in a test table on your MySQL database server. We tried updating a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error'); - $err = TRUE; - } - else { - $success[] = 'UPDATE'; - } - - // Test DELETE. - $query = 'DELETE FROM drupal_install_test'; - $result = mysql_query($query); - if ($error = mysql_error()) { - drupal_set_message(st('Failed to delete a value from a test table on your MySQL database server. We tried deleting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error'); - $err = TRUE; - } - else { - $success[] = 'DELETE'; - } - - // Test DROP. - $query = 'DROP TABLE drupal_install_test'; - $result = mysql_query($query); - if ($error = mysql_error()) { - drupal_set_message(st('Failed to drop a test table from your MySQL database server. We tried dropping a table with the command %query and MySQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error'); - $err = TRUE; - } - else { - $success[] = 'DROP'; - } - - if ($err) { - return FALSE; - } - - mysql_close($connection); - return TRUE; -} === modified file 'includes/bootstrap.inc' --- includes/bootstrap.inc 2008-01-10 22:47:16 +0000 +++ includes/bootstrap.inc 2008-02-27 05:22:04 +0000 @@ -274,7 +274,7 @@ function conf_init() { global $base_url, $base_path, $base_root; // Export the following settings.php variables to the global namespace - global $db_url, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access; + global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access; $conf = array(); if (file_exists('./'. conf_path() .'/settings.php')) { @@ -462,11 +462,9 @@ function variable_get($name, $default) { function variable_set($name, $value) { global $conf; - $serialized_value = serialize($value); - db_query("UPDATE {variable} SET value = '%s' WHERE name = '%s'", $serialized_value, $name); - if (!db_affected_rows()) { - @db_query("INSERT INTO {variable} (name, value) VALUES ('%s', '%s')", $name, $serialized_value); - } + $fields['name'] = $name; + $update['value'] = $fields['value'] = serialize($value); + db_insert('variable_set', 'variable')->fields($fields)->duplicateUpdate($update)->execute(); cache_clear_all('variables', 'cache'); @@ -756,24 +754,33 @@ function request_uri() { function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { global $user, $base_root; - // Prepare the fields to be logged - $log_message = array( - 'type' => $type, - 'message' => $message, - 'variables' => $variables, - 'severity' => $severity, - 'link' => $link, - 'user' => $user, - 'request_uri' => $base_root . request_uri(), - 'referer' => referer_uri(), - 'ip' => ip_address(), - 'timestamp' => time(), - ); - - // Call the logging hooks to log/process the message - foreach (module_implements('watchdog', TRUE) as $module) { - module_invoke($module, 'watchdog', $log_message); + static $in_error_state = FALSE; + + // It is possible that the error handling will itself trigger an error. In that case, we could + // end up in an infinite loop. To avoid that, we implement a simple static semaphore. + if (!$in_error_state) { + $in_error_state = TRUE; + + // Prepare the fields to be logged + $log_message = array( + 'type' => $type, + 'message' => $message, + 'variables' => $variables, + 'severity' => $severity, + 'link' => $link, + 'user' => $user, + 'request_uri' => $base_root . request_uri(), + 'referer' => referer_uri(), + 'ip' => ip_address(), + 'timestamp' => time(), + ); + + // Call the logging hooks to log/process the message + foreach (module_implements('watchdog', TRUE) as $module) { + module_invoke($module, 'watchdog', $log_message); + } } + $in_error_state = FALSE; } /** @@ -947,9 +954,9 @@ function _drupal_bootstrap($phase) { break; case DRUPAL_BOOTSTRAP_DATABASE: - // Initialize the default database. + // Initialize the database system. Note that the connection + // won't be initialized until it is actually requested. require_once './includes/database.inc'; - db_set_active(); break; case DRUPAL_BOOTSTRAP_ACCESS: === modified file 'includes/cache.inc' --- includes/cache.inc 2008-01-29 11:36:06 +0000 +++ includes/cache.inc 2008-02-28 07:22:13 +0000 @@ -28,7 +28,6 @@ function cache_get($cid, $table = 'cache // If the data is permanent or we're not enforcing a minimum cache lifetime // always return the cached data. if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) { - $cache->data = db_decode_blob($cache->data); if ($cache->serialized) { $cache->data = unserialize($cache->data); } @@ -100,16 +99,24 @@ function cache_get($cid, $table = 'cache * A string containing HTTP header information for cached pages. */ function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) { - $serialized = 0; + $fields = array( + 'serialized' => 0, + 'created' => time(), + 'expire' => $expire, + 'headers' => $headers, + ); if (is_object($data) || is_array($data)) { - $data = serialize($data); - $serialized = 1; + $fields['data'] = serialize($data); + $fields['serialized'] = 1; } - $created = time(); - db_query("UPDATE {". $table ."} SET data = %b, created = %d, expire = %d, headers = '%s', serialized = %d WHERE cid = '%s'", $data, $created, $expire, $headers, $serialized, $cid); - if (!db_affected_rows()) { - @db_query("INSERT INTO {". $table ."} (cid, data, created, expire, headers, serialized) VALUES ('%s', %b, %d, %d, '%s', %d)", $cid, $data, $created, $expire, $headers, $serialized); + else { + $fields['data'] = $data; } + + $update = $fields; + $fields['cid'] = $cid; + + db_insert('cache_set', $table)->fields($fields)->duplicateUpdate($update)->execute(); } /** @@ -169,14 +176,14 @@ function cache_clear_all($cid = NULL, $t else { if ($wildcard) { if ($cid == '*') { - db_query("DELETE FROM {". $table ."}"); + db_delete('cache_clear_all_complete', $table)->execute(); } else { - db_query("DELETE FROM {". $table ."} WHERE cid LIKE '%s%%'", $cid); + db_delete('cache_clear_all_cid_like', $table)->condition('cid', 'LIKE', $cid .'%')->execute(); } } else { - db_query("DELETE FROM {". $table ."} WHERE cid = '%s'", $cid); + db_delete('cache_clear_all_cid_equals', $table)->condition('cid', $cid)->execute(); } } } === modified file 'includes/database.inc' --- includes/database.inc 2008-01-08 16:03:31 +0000 +++ includes/database.inc 2008-03-01 04:04:07 +0000 @@ -3,7 +3,7 @@ /** * @file - * Wrapper for database interface code. + * Base classes for the database layer. */ /** @@ -18,13 +18,18 @@ define('DB_ERROR', 'a515ac9c2796ca0e23ad * @{ * Allow the use of different database servers using the same code base. * - * Drupal provides a slim database abstraction layer to provide developers with - * the ability to support multiple database servers easily. The intent of this - * layer is to preserve the syntax and power of SQL as much as possible, while - * letting Drupal control the pieces of queries that need to be written - * differently for different servers and provide basic security checks. + * Drupal provides a database abstraction layer to provide developers with + * the ability to support multiple database servers easily. The intent of + * 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 + * a unified way. It also provides a structured interface for dynamically + * constructing queries when appropriate, and enforcing security checks and + * similar good practices. * - * Most Drupal database queries are performed by a call to db_query() or + * 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 * db_query_range(). Module authors should also consider using pager_query() for * queries that return results that need to be presented on multiple pages, and * tablesort_sql() for generating appropriate queries for sortable tables. @@ -37,186 +42,2059 @@ define('DB_ERROR', 'a515ac9c2796ca0e23ad * one would instead call the Drupal functions: * @code * $result = db_query_range('SELECT n.title, n.body, n.created - * FROM {node} n WHERE n.uid = %d', $uid, 0, 10); - * while ($node = db_fetch_object($result)) { + * FROM {node} n WHERE n.uid = :uid', array(':uid' => $uid), 0, 10); + * foreach($result as $record) { * // Perform operations on $node->body, etc. here. * } * @endcode * Curly braces are used around "node" to provide table prefixing via - * db_prefix_tables(). 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. Finally, note the - * common pattern of iterating over the result set using db_fetch_object(). + * 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. + * Finally, note the PDO-based ability to foreach() over the result set. + * + * + * INSERT, UPDATE, and DELETE queries need special care in order to behave + * consistently across all different databases. Therefore, they use a special + * object-oriented API for defining a query structurally. For example, rather than + * @code + * INSERT INTO node (nid, title, body) VALUES (1, 'my title', 'my body') + * @endcode + * one would instead write: + * @code + * $fields = array('nid' => 1, 'title' => 'my title', 'body' => 'my body'); + * db_insert('my_query', 'node')->fields($fields)->execute(); + * @endcode + * This method allows databases that need special data type handling to do so, + * while also allowing optimizations such as multi-insert queries. UPDATE and DELETE + * queries have a similar pattern. */ + /** - * Perform an SQL query and return success or failure. + * Base Database API class. * - * @param $sql - * A string containing a complete SQL query. %-substitution - * parameters are not supported. + * 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. + * + * @link http://us.php.net/manual/en/ref.pdo.php + */ +abstract class DatabaseConnection extends PDO { + + /** + * Reference to the last statement that was executed. + * + * We only need this for the legacy db_affected_rows() call, which will be removed. + * + * @var DatabaseStatement + * @todo Remove this variable. + */ + public $lastStatement; + + function __construct($dsn, $username, $password, $driver_options = array()) { + $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; // Because the other methods don't seem to work right. + parent::__construct($dsn, $username, $password, $driver_options); + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DatabaseStatement', array($this))); + } + + /** + * Return the default query options for any given query. + * + * A given query can be customized with a number of option flags in an associative array. + * + * return_affected - If true, this method will return the number of rows + * affected by the previous query. If false, this function will return + * the executed statement. It should be set to TRUE for INSERT, UPDATE, + * and DELETE queries and FALSE otherwise. In most cases, this will be set + * by the database system correctly and a module author should not set it. + * + * 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. See + * http://www.php.net/PDOStatement-fetch + * + * target - The database "target" against which to execute a query. Valid 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 is not available, it + * will silently fall back to the "default" target. If multiple databases connections + * are specified with the same target, one will be selected at random for the duration + * of the request. + * + * throw_exception - By default, the database system will catch any errors 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 supress that behavior and simply return NULL on failure, + * set this option to FALSE. + * + * @return + * An array of default query options. + */ + protected function defaultOptions() { + return array( + 'target' => 'default', + 'fetch' => PDO::FETCH_OBJ, + 'return_affected' => FALSE, + 'throw_exception' => TRUE, + ); + } + + /** + * Returns whether or not this database connection supports transactions. + * + * @return + * TRUE if this connection supports transactions, FALSE if not. + */ + public function transactions() { + return $this->transactionSupport; + } + + /** + * Append a database prefix to all tables in a query. + * + * 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 + * tables, allowing Drupal to coexist with other systems in the same database if + * necessary. + * + * @param $sql + * A string containing a partial or entire SQL query. + * @return + * The properly-prefixed string. + */ + protected function prefixTables($sql) { + global $db_prefix; + + if (is_array($db_prefix)) { + if (array_key_exists('default', $db_prefix)) { + $tmp = $db_prefix; + unset($tmp['default']); + foreach ($tmp as $key => $val) { + $sql = strtr($sql, array('{'. $key .'}' => $val . $key)); + } + return strtr($sql, array('{' => $db_prefix['default'] , '}' => '')); + } + else { + foreach ($db_prefix as $key => $val) { + $sql = strtr($sql, array('{'. $key .'}' => $val . $key)); + } + return strtr($sql, array('{' => '' , '}' => '')); + } + } + else { + return strtr($sql, array('{' => $db_prefix , '}' => '')); + } + } + + /** + * 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. This method statically caches those + * prepared statements, reusing them when possible. + * + * @param $query + * The query string to execute, as a prepared statement. + * @param $args + * An array of arguments for the prepared statement. If the prepared + * statement uses ? placeholders, this array must be an indexed array. + * If it contains named placeholders, it must be an associative array. + * @param $options + * An associative array of options to control how the query is run. See + * the documentation for DatabaseConnection::defaultOptions() for details. + * @return + * If $options['affected_rows'] is TRUE, the query is assumed to be + * a modifier query (INSERT, UPDATE, DELETE) and the number of affected + * rows is returned. Otherwise, the executed prepared statement object + * is returned. If there is an error, this method will return NULL. + */ + protected function runQuery($query, Array $args, $options = array()) { + + static $statements = array(); + + try { + $query = self::prefixTables($query); + + // Cache each prepared statement, keyed by the query itself. This way, + // we get the benefit of prepared statement caching without any extra + // work by the module author. + if (!isset($statements[$query])) { + $statements[$query] = $this->prepare($query); + } + + $options += $this->defaultOptions(); + $statements[$query]->execute($args, $options); + + $this->lastStatement = $statements[$query]; + + return $options['return_affected'] ? $statements[$query]->rowCount() : $statements[$query]; + } + catch (PDOException $e) { + if (!function_exists('module_implements')) { + _db_need_install(); + } + watchdog('database', var_export($e, TRUE) . $e->getMessage(), NULL, WATCHDOG_ERROR); + if ($options['throw_exception']) { + throw $e; + } + return NULL; + } + } + + /** + * Execute an arbitrary query string against this database. + * + * @param $query + * A string containing an SQL query. + * @param $args + * An array of values to substitute into the query at placeholder markers. + * @param $options + * An array of options on the query. + * @return + * A database query result resource, or NULL if the query was not executed + * correctly. + */ + public function query($query, Array $args, Array $options = array()) { + return $this->runQuery($query, $args, $options); + } + + /** + * Prepare and return a SELECT query object with the specified ID. + * + * @see SelectQuery + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $options + * An array of options on the query. + * @return + * A new SelectQuery object. + */ + public function select($query_id, Array $options = array()) { + $class_type = 'SelectQuery_'. $this->driver(); + return new $class_type($query_id, $this, $options); + } + + /** + * Prepare and return an INSERT query object with the specified ID. + * + * @see InsertQuery + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $options + * An array of options on the query. + * @return + * A new InsertQuery object. + */ + public function insert($query_id, $table, Array $options = array()) { + $class_type = 'InsertQuery_'. $this->driver(); + return new $class_type($query_id, $this, $table, $options); + } + + /** + * Prepare and return an UPDATE query object with the specified ID. + * + * @see UpdateQuery + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $options + * An array of options on the query. + * @return + * A new UpdateQuery object. + */ + public function update($query_id, $table, Array $options = array()) { + $class_type = 'UpdateQuery_'. $this->driver(); + return new $class_type($query_id, $this, $table, $options); + } + + /** + * Prepare and return a DELETE query object with the specified ID. + * + * @see DeleteQuery + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $options + * An array of options on the query. + * @return + * A new DeleteQuery object. + */ + public function delete($query_id, $table, Array $options = array()) { + $class_type = 'DeleteQuery_'. $this->driver(); + return new $class_type($query_id, $this, $table, $options); + } + + /** + * Returns a DatabaseSchema object for manipulating the schema of this database. + * + * This method will lazy-load the appropriate schema library file. + * + * @return + * The DatabaseSchema object for this connection. + */ + public function schema() { + static $schema; + if (empty($schema)) { + require_once('./includes/schema.inc'); + require_once('./includes/schema.'. $this->driver() .'.inc'); + $class_type = 'DatabaseSchema_'. $this->driver(); + $schema = new $class_type($this); + } + return $schema; + } + + /** + * Returns a new DatabaseTransaction object on this connection. + * + * @see DatabaseTransaction + */ + public function startTransaction() { + $class_type = 'DatabaseTransaction_'. $this->driver(); + return new $class_type($this); + } + + /** + * 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) { + return preg_replace('/[^A-Za-z0-9_]+/', '', $string); + } + + /** + * Runs a limited-range query on this database object. + * + * Use this as a substitute for ->query() when a subset of the query is to be + * returned. + * User-supplied arguments to the query should be passed in as separate parameters + * so that they can be properly escaped to avoid SQL injection attacks. + * + * @param $query + * A string containing an SQL query. + * @param $args + * An array of values to substitute into the query at placeholder markers. + * @param $from + * The first result row to return. + * @param $count + * The maximum number of result rows to return. + * @param $options + * An array of options on the query. + * @return + * A database query result resource, or NULL if the query was not executed + * correctly. + */ + abstract public function queryRange($query, Array $args, $from, $count, Array $options); + + /** + * Runs a SELECT query and stores its results in a temporary table. + * + * Use this as a substitute for ->query() when the results need to stored + * in a temporary table. Temporary tables exist for the duration of the page + * request. + * User-supplied arguments to the query should be passed in as separate parameters + * so that they can be properly escaped to avoid SQL injection attacks. + * + * Note that if you need to know how many results were returned, you should do + * a SELECT COUNT(*) on the temporary table afterwards. + * + * @param $query + * A string containing a normal SELECT SQL query. + * @param $args + * An array of values to substitute into the query at placeholder markers. + * @param $tablename + * The name of the temporary table to select into. This name will not be + * prefixed as there is no risk of collision. + * @return + * A database query result resource, or FALSE if the query was not executed + * correctly. + */ + abstract function queryTemporary($query, Array $args, $tablename); + + /** + * Returns the type of database driver. + * + * This is not necessarily the same as the type of the database itself. + * For instance, there could be two MySQL drivers, mysql and mysql_mock. + * This function would return different values for each, but both would + * return "mysql" for databaseType(). + */ + abstract public function driver(); + + /** + * Determine if this driver supports transactions. + */ + abstract public function supportsTransactions(); + + /** + * Returns the type of the database being accessed. + */ + abstract public function databaseType(); + +} + +/** + * Primary front-controller for the database system. + * + * This class is uninstantiatable and un-extendable. It acts to encapsulate + * all control and shepherding of database connections into a single location + * without the use of globals. + * + */ +abstract class Database { + + /** + * An nested array of all active connections. It is keyed by database name and target. + * + * @var array + */ + static protected $connections = array(); + + /** + * A processed copy of the database connection information from settings.php + * + * @var array + */ + static protected $databaseInfo = NULL; + + /** + * The key of the currently active database connection. + * + * @var string + */ + static protected $activeKey = 'default'; + + /** + * Gets the active connection object for the specified target. + * + * @return + * The active connection object. + */ + final public static function getActiveConnection($target = 'default') { + return self::getConnection(self::$activeKey, $target); + } + + /** + * Gets the connection object for the specified database key and target. + * + * @return + * The corresponding connection object. + */ + final public static function getConnection($key = 'default', $target = 'default') { + if (!isset(self::$connections[$key][$target])) { + self::openConnection($key, $target); + } + + return isset(self::$connections[$key][$target]) ? self::$connections[$key][$target] : NULL; + } + + /** + * Determine if there is an active connection. + * + * Note that this method will return FALSE if no connection has been established + * yet, even if one could be. + * + * @return + * TRUE if there is at least one database connection established, FALSE otherwise. + */ + final public static function isActiveConnection() { + return !empty(self::$connections); + } + + /** + * Set the active connection to the specified key. + * + * @return + * The previous database connection key. + */ + final public static function setActiveConnection($key = 'default') { + if (empty(self::$databaseInfo)) { + self::parseConnectionInfo(); + } + + if (!empty(self::$databaseInfo[$key])) { + $old_key = self::$activeKey; + self::$activeKey = $key; + return $old_key; + } + } + + /** + * Parse out the database connection information specified in the config + * file and specify defaults where necessary. + */ + final protected static function parseConnectionInfo() { + global $databases; + + if (empty($databases)) { + _db_need_install(); + } + $databaseInfo = $databases; + + // If no database key is specified, default to default. + if (!is_array($databaseInfo)) { + $databaseInfo = array('default' => $databaseInfo); + } + + foreach ($databaseInfo as $index => $info) { + // If no targets are specified, default to one default. + if (!is_array($databaseInfo[$index])) { + $databaseInfo[$index] = array('default' => $info); + } + + foreach ($databaseInfo[$index] as $target => $value) { + // If there is no "driver" property, then we assume it's an array of possible connections for + // this target. Pick one at random. That allows us to have, for example, multiple slave servers. + if (empty($value['driver'])) { + $databaseInfo[$index][$target] = $databaseInfo[$index][$target][mt_rand(0, count($databaseInfo[$index][$target]) - 1)]; + } + } + } + + self::$databaseInfo = $databaseInfo; + } + + /** + * Open a connection to the server specified by the given + * key and target. + * + * @param $key + * @param $target + */ + final protected static function openConnection($key, $target) { + if (empty(self::$connectionInfo)) { + self::parseConnectionInfo(); + } + try { + // If the requested database does not exist then it is an unrecoverable error. + // If the requested target does not exist, however, we fall back to the default + // target. The target is typically either "default" or "slave", indicating to + // use a slave SQL server if one is available. If it's not available, then the + // default/master server is the correct server to use. + if (!isset(self::$databaseInfo[$key])) { + throw new Exception('DB does not exist'); + } + if (!isset(self::$databaseInfo[$key][$target])) { + $target = 'default'; + } + + if (!$driver = self::$databaseInfo[$key][$target]['driver']) { + throw new Exception('Drupal is not set up'); + } + $driver_class = 'DatabaseConnection_'. $driver; + $driver_file ='./includes/database.'. $driver .'.inc'; + require_once($driver_file); + self::$connections[$key][$target] = new $driver_class(self::$databaseInfo[$key][$target]); + } + catch (Exception $e) { + _db_need_install(); + throw $e; + // TODO. error handling. + } + } +} + +/** + * A wrapper class for creating and managing database transactions. + * + * Not all databases or database configurations support transactions. For + * example, MySQL MyISAM tables do not. It is also easy to begin a transaction + * and then forget to commit it, which can lead to connection errors when + * another transaction is started. + * + * This class acts as a wrapper for transactions. To begin a transaction, + * simply instantiate it. When the object goes out of scope and is destroyed + * it will automatically commit. It also will check to see if the specified + * connection supports transactions. If not, it will simply skip any transaction + * commands, allowing user-space code to proceed normally. The only difference + * is that rollbacks won't actually do anything. + * + * In the vast majority of cases, you should not instantiate this class directly. + * Instead, call ->startTransaction() from the appropriate connection object. + */ +class DatabaseTransaction { + + /** + * The connection object for this transaction. + * + * @var DatabaseConnection + */ + protected $connection; + + /** + * Whether or not this connection supports transactions. + * + * This can be derived from the connection itself with a method call, + * but is cached here for performance. + * + * @var boolean + */ + protected $supportsTransactions; + + /** + * Whether or not this transaction has been rolled back. + * + * @var boolean + */ + protected $hasRolledBack = FALSE; + + /** + * Whether or not this transaction has been committed. + * + * @var boolean + */ + protected $hasCommitted = FALSE; + + public function __construct(DatabaseConnection $connection) { + $this->connection = $connection; + $this->supportsTransactions = $connection->supportsTransactions(); + + if ($this->supportsTransactions) { + $connection->beginTransaction(); + } + } + + /** + * Commit this transaction. + */ + public function commit() { + if ($this->supportsTransactions) { + $this->connection->commit(); + $this->hasCommitted = TRUE; + } + } + + /** + * Roll back this transaction. + */ + public function rollBack() { + if ($this->supportsTransactions) { + $this->connection->rollBack(); + $this->hasRolledBack = TRUE; + } + } + + /** + * Determine if this transaction has already been rolled back. + * + * @return + * TRUE if the transaction has been rolled back, FALSE otherwise. + */ + public function hasRolledBack() { + return $this->hasRolledBack; + } + + public function __destruct() { + if ($this->supportsTransactions && !$this->hasRolledBack && !$this->hasCommitted) { + $this->connection->commit(); + } + } + +} + +/** + * Prepared statement class. + * + * PDO allows us to extend the PDOStatement class to provide additional functionality beyond + * that offered by default. We do need extra functionality. By default, this class is not + * driver-specific. If a given driver needs to set a custom statement class, it may do so + * in its constructor. + * + * @link http://us.php.net/manual/en/ref.pdo.php + */ +class DatabaseStatement extends PDOStatement { + + public $dbh; + + protected function __construct($dbh) { + $this->dbh = $dbh; + $this->setFetchMode(PDO::FETCH_OBJ); + } + + /** + * Executes a prepared statement + * + * @param $args + * An array of values with as many elements as there are bound parameters in the SQL statement being executed. + * @param $options + * An array of options for this query. + * @return + * TRUE on success, or FALSE on failure. + */ + public function execute($args, $options) { + if (is_string($options['fetch'])) { + $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']); + } + else { + $this->setFetchMode($options['fetch']); + } + return parent::execute($args); + } + + /** + * Returns an entire single column of a result set as an indexed array. + * + * Note that this method will run the result set to the end. + * + * @param $index + * The index of the column number to fetch. + * @return + * An indexed array. + */ + public function fetchCol($index = 0) { + return $this->fetchAll(PDO::FETCH_COLUMN, $index); + } + + /** + * Returns an entire result set as an associative array of stdClass objects, keyed by the named field. + * + * If the given key appears multiple times, later records will overwrite earlier ones. + * + * Note that this method will run the result set to the end. + * + * @param $key + * The name of the field on which to index the array. + * @return + * An associative array. + */ + public function fetchAllAssoc($key) { + $return = array(); + $this->setFetchMode(PDO::FETCH_OBJ); + foreach ($this as $record) { + $return[$record->$key] = $record; + } + return $return; + } + + /** + * Returns the entire result set as a single associative array. + * + * This method is only useful for two-column result sets. It will return + * an associative array where the key is one column from the result set + * and the value is another field. In most cases, the default of the first two + * columns is appropriate. + * + * Note that this method will run the result set to the end. + * + * @param $key_index + * The numeric index of the field to use as the array key. + * @param $value_index + * The numeric index of the field to use as the array value. + * @return + * An associative array. + */ + public function fetchAllKeyed($key_index = 0, $value_index = 1) { + $return = array(); + $this->setFetchMode(PDO::FETCH_NUM); + foreach ($this as $record) { + $return[$record[$key_index]] = $record[$value_index]; + } + return $return; + } + + /** + * Return a single field out of the current + * + * @param $index + * The numeric index of the field to return. Defaults to the first field. + * @return + * A single field from the next record. + */ + public function fetchOne($index = 0) { + return $this->fetchColumn($index); + } + + /** + * Fetches the next row and returns it as an associative array. + * + * This method corresponds to PDOStatement::fetchObject(), + * but for associative arrays. For some reason PDOStatement does + * not have a corresponding array helper method, so one is added. + * + * @return + * An associative array. + */ + public function fetchAssoc() { + return $this->fetch(PDO::FETCH_ASSOC); + } +} + +/** + * Interface for a conditional clause in a query. + */ +interface QueryConditionInterface { + + /** + * Helper function to build most common conditional clauses. + * + * This method can take a variable number of parameters. If called with two + * parameters, they are taken as $field and $value with $operator having a value + * of =. + * + * @param $field + * The name of the field to check. + * @param $operator + * The comparison operator, such as =, <, or >=. It also accepts more complex + * options such as IN, LIKE, or BETWEEN. + * @param $value + * The value to test the field against. In most cases, this is a scalar. For more + * complex options, it is an array. The meaning of each element in the array is + * dependent on the $operator. + * @param $num_args + * For internal use only. This argument is used to track the recursive calls when + * processing complex conditions. + * @return + * The called object. + */ + public function condition($field, $operator = NULL, $value = NULL, $num_args = NULL); + + /** + * Add an arbitrary WHERE clause to the query. + * + * @param $snippet + * A portion of a WHERE clause as a prepared statement. It must use named placeholders, + * not ? placeholders. + * @param $args + * An associative array of arguments. + * @return + * The called object. + */ + public function where($snippet, $args = array()); + + /** + * Gets a complete list of all conditions in this conditional clause. + * + * If there is at least one condition, there will also be an array key #conjunction + * that represents the conjunction to use on the conditions. That is, if the + * conjunction is AND, the different clauses will be ANDed together. If there are no + * conditions, an empty string is returned. + */ + public function conditions(); + + /** + * Gets a complete list of all values to insert into the prepared statement. + * + * @returns + * An associative array of placeholders and values. + */ + public function values(); +} + +/** + * Base class for the query builders. + * + * All query builders inherit from a common base class. Any built query has a unique + * queryId, which is used to uniquely identify a query to hook_query_alter(). + * + */ +abstract class Query implements QueryConditionInterface { + + /** + * The ID of the query, which will be used for query_alter() hooks. + * + * @var string + */ + protected $queryId; + + /** + * The connection object on which to run this query. + * + * @var DatabaseConnection + */ + protected $connection; + + /** + * The query options to pass on to the connection object. + * + * @var array + */ + protected $queryOptions; + + /** + * The condition object for this query. Condition handling is handled via + * composition. + * + * @var DatabaseCondition + */ + protected $condition; + + public function __construct($query_id, DatabaseConnection $connection, $options) { + $this->queryId = $query_id; + $this->connection = $connection; + $this->queryOptions = $options; + + $this->condition = new DatabaseCondition('AND'); + } + + public function condition($field, $operator = NULL, $value = NULL, $num_args = NULL) { + if (!isset($num_args)) { + $num_args = func_num_args(); + } + $this->condition->condition($field, $operator, $value, $num_args); + return $this; + } + + public function conditions() { + return $this->condition->conditions(); + } + + public function values() { + return $this->condition->values(); + } + + public function where($snippet, $args = array()) { + $this->condition->where($snippet, $args); + return $this; + } + + /** + * Parse an array of conditionals into a WHERE clause. + */ + protected function parseWhere($array) { + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = $this->parseWhere($value); + } + } + $conjunction = ' '. $array['#conjunction'] .' '; + unset($array['#conjunction']); + return '('. implode($conjunction, $array) .')'; + } + + /** + * Run the query against the database. + */ + abstract protected function execute(); + + /** + * Returns the query as a prepared statement string. + */ + abstract protected function __toString(); +} + +/** + * General class for an abstracted INSERT operation. + */ +abstract class InsertQuery extends Query { + + /** + * The table on which to insert. + * + * @var string + */ + protected $table; + + /** + * Whether or not this query is "delay-safe". Different database drivers + * may or may not implement this feature in their own ways. + * + * @var boolean + */ + protected $delay; + + /** + * An array of fields on which to insert. + * + * @var array + */ + protected $insertFields = array(); + + /** + * A nested array of values to insert. + * + * $insertValues itself is an array of arrays. Each sub-array is an array of + * field names to values to insert. Whether multiple insert sets + * will be run in a single query or multiple queries is left to individual drivers + * to implement in whatever manner is most efficient. The order of values in each + * sub-array must match the order of fields in $insertFields. + * + * @var string + */ + protected $insertValues = array(); + + /** + * An array of fields to update in case the initial insert fails. + * + * The implementation details of this operation are database-dependant. + * + * @var array + */ + protected $updateFields = array(); + + /** + * An array of values to update in case the initial insert fails. + * + * The implementation details of this operation are database-dependant. + * + * @var array + */ + protected $updateValues = array(); + + public function __construct($query_id, $connection, $table, Array $options = array()) { + $options['return_affected'] = TRUE; + $options += array('delay' => FALSE); + parent::__construct($query_id, $connection, $options); + $this->table = $table; + } + + /** + * Add a set of field->value pairs to be inserted. + * + * This method may be called multiple times. If it is, multiple sets of values + * will be inserted. The order of fields must be the same each time. + * + * @param $fields + * An associative array of fields to insert into the database. The array keys + * are the field names while the values are the values to insert. + * @return + * The called object. + */ + public function fields(Array $fields) { + if (empty($this->insertFields)) { + $this->insertFields = array_keys($fields); + } + $this->insertValues[] = array_values($fields); + + return $this; + } + + /** + * Add a set of field->value pairs to be updated in case the key of the table + * already exists. + * + * The implementation of this functionality is driver-specific. On MySQL, it + * corresponds directly to "INSERT ... ON DUPLICATE KEY UPDATE..." The implementation + * for other databases is left up to each driver to determine. + * + * @param $fields + * An associative array of fields to update in the database. The array keys + * are the field names while the values are the values to which to set those fields. + * @return + * The called object. + */ + public function duplicateUpdate(Array $fields) { + $max_placeholder = 0; + $this->updateFields = array(); + $this->updateValues = array(); + foreach ($fields as $field => $value) { + // If $field is numeric, the $value is the entire clause. + if (is_numeric($field)) { + $this->updateFields[] = $value; + } + else { + $placeholder = ':db_update_placeholder_'. ($max_placeholder++); + $this->updateFields[] = $field .'='. $placeholder; + $this->updateValues[$placeholder] = $value; + } + } + return $this; + } + + public function execute() { + drupal_alter('query', $this->queryId, $this); + + // Each insert happens in its own query in the degenerate case. However, + // we wrap it in a transaction so that it is atomic where possible. On many + // databases, such as SQLite, this is also a notable performance boost. + $transaction = $this->connection->startTransaction(); + foreach ($this->insertValues as $insert_values) { + + // There is no good DB-agnostic equivalent of MySQL's ON DUPLICATE KEY UPDATE + // functionality. Implementing drivers will need to provide their own + // database-specific implementation in their child class. + + $num_affected = $this->connection->runQuery((string)$this, $insert_values, $this->queryOptions); + } + $transaction->commit(); + + return $this->connection->lastInsertId(); + } + + public function __toString() { + $placeholders = array_fill(0, count($this->insertFields), '?'); + + return 'INSERT INTO {'. $this->table .'} ('. implode(', ', $this->insertFields) .') VALUES ('. implode(', ', $placeholders) .')'; + } +} + +/** + * General class for an abstracted DELETE operation. + * + * The conditional WHERE handling of this class is all inherited from Query. + */ +abstract class DeleteQuery extends Query { + + /** + * The table from which to delete. + * + * @var string + */ + protected $table; + + public function __construct($query_id, DatabaseConnection $connection, $table, Array $options = array()) { + $options['return_affected'] = TRUE; + $options += array('delay' => FALSE); + parent::__construct($query_id, $connection, $options); + $this->table = $table; + } + + public function execute() { + drupal_alter('query', $this->queryId, $this); + $values = array(); + if ($this->condition->conditions()) { + $values = $this->condition->values(); + } + + return $this->connection->runQuery((string)$this, $values, $this->queryOptions); + } + + public function __toString() { + $query = 'DELETE FROM {'. $this->connection->escapeTable($this->table) .'} '; + + $conditions = $this->condition->conditions(); + if ($conditions) { + $query .= "\nWHERE ". $this->parseWhere($conditions); + } + + return $query; + } +} + +/** + * General class for an abstracted UPDATE operation. + * + * The conditional WHERE handling of this class is all inherited from Query. + */ +abstract class UpdateQuery extends Query { + + /** + * The table to update. + * + * @var string + */ + protected $table; + + /** + * An array of fields that will be updated. + * + * @var array + */ + protected $fields; + + /** + * An array of values to update to. + * + * @var array + */ + protected $values; + + public function __construct($query_id, DatabaseConnection $connection, $table, Array $options = array()) { + $options['return_affected'] = TRUE; + $options += array('delay' => FALSE); + parent::__construct($query_id, $connection, $options); + $this->table = $table; + } + + /** + * Add a set of field->value pairs to be updated. + * + * @param $fields + * An associative array of fields to write into the database. The array keys + * are the field names while the values are the values to which to set them. + * @return + * The called object. + */ + public function fields(Array $fields) { + // Parse out the fields into placeholders and values. We need to do this + // on assignment to avoid having to double-iterate, once to get + // the query string and once to get the values array. + $max_placeholder = 0; + $this->fields = array(); + foreach ($fields as $field => $value) { + $placeholder = ':db_update_placeholder_'. ($max_placeholder++); + $this->fields[] = $field .'='. $placeholder; + $this->values[$placeholder] = $value; + } + + return $this; + } + + public function execute() { + drupal_alter('query', $this->queryId, $this); + $update_values = $this->values; + + $conditions = $this->condition->conditions(); + if ($conditions) { + $update_values = array_merge($update_values, $this->condition->values()); + } + return $this->connection->runQuery((string)$this, $update_values, $this->queryOptions); + } + + public function __toString() { + $query = 'UPDATE {'. $this->connection->escapeTable($this->table) .'} SET '. implode(', ', $this->fields); + + $conditions = $this->condition->conditions(); + if ($conditions) { + $query .= "\nWHERE ". $this->parseWhere($conditions); + } + + return $query; + } + +} + +/** + * Abstract query builder for SELECT statements. + */ +abstract class SelectQuery extends Query { + + /** + * The fields to SELECT. + * + * @var array + */ + protected $fields = array(); + + /** + * The tables against which to JOIN. + * + * This property is a nested array. Each entry is an array representing + * a single table against which to join. The structure of each entry is: + * + * array( + * 'type' => $join_type (one of INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN), + * 'table' => $name_of_table, + * 'alias' => $alias_of_the_table, + * 'condition' => $condition_clause_on_which_to_join, + * ) + * + * @var array + */ + protected $tables = array(); + + /** + * The values to insert into the prepared statement of this query. + * + * @var array + */ + protected $values = array(); + + /** + * The fields by which to order this query. + * + * This is an associative array. The keys are the fields to order, and the value + * is the direction to order, either ASC or DESC. + * + * @var array + */ + protected $order = array(); + + /** + * The fields by which to group. + * + * @var array + */ + protected $group = array(); + + /** + * The conditional object for the HAVING clause. + * + * @var DatabaseCondition + */ + protected $having; + + /** + * Whether or not this query should be DISTINCT + * + * @var boolean + */ + protected $distinct = FALSE; + + /** + * The range limiters for this query. + * + * @var array + */ + protected $limits; + + public function __construct($query_id, DatabaseConnection $connection, $options = array()) { + parent::__construct($query_id, $connection, $options); + $this->having = new DatabaseCondition('AND'); + } + + public function execute() { + drupal_alter('query', $this->queryId, $this); + + if (!empty($this->limit)) { + return $this->connection->queryRange((string)$this, $this->values(), $this->limit['start'], $this->limit['length'], $this->queryOptions); + } + + return $this->connection->runQuery((string)$this, $this->values(), $this->queryOptions); + } + + /** + * Retuns a duplicate of this query object, but with fields set to "COUNT(*)". + * + * @return + * A new SelectQuery object. + */ + public function countQuery() { + $count_query = clone($this); + + $count_query->setFields('COUNT(*)'); + + return $count_query; + } + + /** + * Sets this query to be DISTINCT. + * + * @param $distinct + * TRUE to flag this query DISTINCT, FALSE to disable it. + * @return + * The called object. + */ + public function distinct($distinct = TRUE) { + $this->distinct = $distinct; + return $this; + } + + /** + * Adds more fields to be SELECTed. + * + * If there are already fields to be selcted, this method will concatenate to the list. + * + * @param $field + * This method takes a variable number of parameters. Each parameter + * is treated as a field to be placed into the SELECT clause. A compound + * field or aliased field will be accepted literally. + * @return + * The called object. + */ + public function fields() { + $args = func_get_args(); + $this->fields = array_merge($this->fields, $args); + return $this; + } + + /** + * Sets the fields to be SELECTed. + * + * If there are already fields to be selected, this method will overwrite the list. + * + * @param $fields + * This method takes a variable number of parameters. Each parameter + * is treated as a field to be placed into the SELECT clause. A compound + * field or aliased field will be accepted literally. + * @return + * The called object. + */ + public function setFields() { + $this->fields = func_get_args(); + return $this; + } + + /** + * Default Join against another table in the database. + * + * This method is a convenience method for innerJoin(). + * + * @param $table + * The table against which to join. + * @param $alias + * The alias for the table. In most cases this should be the first letter + * of the table, or the first letter of each "word" in the table. + * @param $condition + * The condition on which to join this table. If the join requires values, + * this clause should use a named placeholder and the value or values to + * insert should be passed in the 4th parameter. For the first table joined + * on a query, this value is ignored as the first table is taken as the base + * table. + * @param $values + * An array of values to replace into the $condition of this join. + * @return + * The called object. + */ + public function join($table, $alias, $condition = NULL, $values = array()) { + return $this->addJoin('INNER', $table, $alias, $condition, $values); + } + + /** + * Inner Join against another table in the database. + * + * @param $table + * The table against which to join. + * @param $alias + * The alias for the table. In most cases this should be the first letter + * of the table, or the first letter of each "word" in the table. + * @param $condition + * The condition on which to join this table. If the join requires values, + * this clause should use a named placeholder and the value or values to + * insert should be passed in the 4th parameter. For the first table joined + * on a query, this value is ignored as the first table is taken as the base + * table. + * @param $values + * An array of values to replace into the $condition of this join. + * @return + * The called object. + */ + public function innerJoin($table, $alias, $condition = NULL, $values = array()) { + return $this->addJoin('INNER', $table, $alias, $condition, $values); + } + + /** + * Left Outer Join against another table in the database. + * + * @param $table + * The table against which to join. + * @param $alias + * The alias for the table. In most cases this should be the first letter + * of the table, or the first letter of each "word" in the table. + * @param $condition + * The condition on which to join this table. If the join requires values, + * this clause should use a named placeholder and the value or values to + * insert should be passed in the 4th parameter. For the first table joined + * on a query, this value is ignored as the first table is taken as the base + * table. + * @param $values + * An array of values to replace into the $condition of this join. + * @return + * The called object. + */ + public function leftJoin($table, $alias, $condition = NULL, $values = array()) { + return $this->addJoin('LEFT OUTER', $table, $alias, $condition, $values); + } + + /** + * Right Outer Join against another table in the database. + * + * @param $table + * The table against which to join. + * @param $alias + * The alias for the table. In most cases this should be the first letter + * of the table, or the first letter of each "word" in the table. + * @param $condition + * The condition on which to join this table. If the join requires values, + * this clause should use a named placeholder and the value or values to + * insert should be passed in the 4th parameter. For the first table joined + * on a query, this value is ignored as the first table is taken as the base + * table. + * @param $values + * An array of values to replace into the $condition of this join. + * @return + * The called object. + */ + public function rightJoin($table, $alias, $condition = NULL, $values = array()) { + return $this->addJoin('RIGHT OUTER', $table, $alias, $condition, $values); + } + + /** + * Join against another table in the database. + * + * This method does the "hard" work of queuing up a table to be joined against. + * In some cases, that may include dipping into the Schema API to find the necessary + * fields on which to join. + * + * @param $table + * The table against which to join. + * @param $alias + * The alias for the table. In most cases this should be the first letter + * of the table, or the first letter of each "word" in the table. + * @param $condition + * The condition on which to join this table. If the join requires values, + * this clause should use a named placeholder and the value or values to + * insert should be passed in the 4th parameter. For the first table joined + * on a query, this value is ignored as the first table is taken as the base + * table. + * @param $values + * An array of values to replace into the $condition of this join. + * @return + * The called object. + */ + protected function addJoin($type, $table, $alias, $condition = NULL, $values = array()) { + if (!isset($conditions)) { + // @todo: Check the Schema API and build a direct equals on the appropriate foreign keys. + } + + $this->tables[] = array( + 'type' => "$type JOIN ", + 'table' => $table, + 'alias' => $alias, + 'condition' => $condition, + ); + + return $this; + } + + /** + * Orders the result set by a given field. + * + * If called multiple times, the query will order by each specified field in the + * order this method is called. + * + * @param $field + * The field on which to order. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * @return + * The called object. + */ + public function orderBy($field, $direction = 'ASC') { + $this->order[$field] = $direction; + return $this; + } + + /** + * Restricts a query to a given range in the result set. + * + * @param $start + * The first record from the result set to return. + * @param $limit + * The number of records to return from the result set. + * @return + * The called object. + */ + public function limit($start, $length) { + $this->limit = array('start' => $start, 'length' => $length); + return $this; + } + + /** + * Groups the result set by the specified field. + * + * @param $field + * The field on which to group. + * @return + * The called object. + */ + public function groupBy($field) { + $this->group[] = $field; + } + + /** + * Helper function to build most common HAVING conditional clauses. + * + * This method is equivalent to condition(), but operates on the HAVING clause + * rather than the WHERE clause. See the note in SelectQuery::having(). + * + * @see SelectQuery::having() + * @see DatabaseCondition::condition() + * @param $field + * The name of the field to check. + * @param $operator + * The comparison operator, such as =, <, or >=. It also accepts more complex + * options such as IN, LIKE, or BETWEEN. + * @param $value + * The value to test the field against. In most cases, this is a scalar. For more + * complex options, it is an array. The meaning of each element in the array is + * dependent on the $operator. + * @param $num_args + * For internal use only. This argument is used to track the recursive calls when + * processing complex conditions. + * @return + * The called object. + */ + public function havingCondition($field, $operator = NULL, $value = NULL, $num_args = NULL) { + if (!isset($num_args)) { + $num_args = func_num_args(); + } + $this->having->condition($field, $operator, $value, $num_args); + return $this; + } + + /** + * Adds a conditional clause to the HAVING portion of the query. + * + * HAVING works in essentially the same way as WHERE, and this method operates the same + * way as where(). HAVING applies after a GROUP BY statement has taken effect, however, + * while WHERE applies before. + * + * @see DatabaseCondition::where() + * @param $snippet + * A portion of a HAVING clause as a prepared statement. It must use named placeholders, + * not ? placeholders. + * @param $args + * An associative array of arguments. + * @return + * The called object. + */ + public function having($snippet, $args = array()) { + $this->having->where($snippet, $args); + return $this; + } + + public function __toString() { + + // SELECT + $query = 'SELECT '; + if ($this->distinct) { + $query .= 'DISTINCT '; + } + + // FIELDS + $query .= implode(', ', $this->fields); + + // FROM + $tables = $this->tables; + $query .= "\nFROM ". $this->connection->escapeTable($tables[0]['table']) .' AS '. $tables[0]['alias']; + unset($tables[0]); + foreach ($tables as $params) { + $query .= "\n". $params['type'] .' JOIN '. $this->connection->escapeTable($params['table']) .' AS '. $params['alias'] .' ON '. $params['condition']; + } + + // WHERE + $conditions = $this->condition->conditions(); + if ($conditions) { + $query .= "\nWHERE ". $this->parseWhere($conditions); + } + + // GROUP BY + if ($this->group) { + $query .= "\nGROUP BY " . implode(', ', $this->group); + } + + // HAVING + $conditions = $this->having->conditions(); + if ($conditions) { + $query .= "\nHAVING ". $this->parseWhere($conditions); + } + + // ORDER BY + if ($this->order) { + $query .= "\nORDER BY "; + foreach ($this->order as $field => $direction) { + $query .= $field .' '. $direction .' '; + } + } + + // LIMIT is database specific, so we can't do it here. + + return $query; + } +} + +/** + * Generic class for a series of conditions in a query. + */ +class DatabaseCondition implements QueryConditionInterface { + + protected $conditions = array(); + protected $values = array(); + + function __construct($conjunction) { + $this->conditions['#conjunction'] = $conjunction; + } + + function condition($field, $operator = NULL, $value = NULL, $num_args = NULL) { + if (!isset($num_args)) { + $num_args = func_num_args(); + } + list($condition, $value) = $this->_DatabaseCondition($field, $operator, $value, $num_args); + $this->conditions[] = $condition; + $this->values += $value; + return $this; + } + + public function where($snippet, $args = array()) { + $this->conditions[] = $snippet; + $this->values += $args; + return $this; + } + + function conditions() { + // If there's only one entry, it's just the default #type, which means + // we don't really have any conditions to speak of. + return (count($this->conditions) > 1) ? $this->conditions : array(); + } + + function values() { + return $this->values; + } + + protected function placeholderMultiple($count, $delimiter, $values) { + static $max_placeholder = 0; + $new_placeholder = $max_placeholder + $count; + for ($i = $max_placeholder; $i < $new_placeholder; ++$i) { + $placeholder = ':db_placeholder_multiple_'. $i; + list(, $arguments[$placeholder]) = each($values); + $placeholders[] = $placeholder; + } + $max_placeholder = $new_placeholder; + return array(implode($delimiter, $placeholders), $arguments); + } + + protected function _DatabaseCondition($field, $operator, $value, $process_type) { + static $max_placeholder = 0; + static $specials = array( + 'BETWEEN' => array('delimiter' => ' AND '), + 'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), + 'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), + ); + switch ($process_type) { + case 1: + return array($field->conditions(), $field->values()); + case 2: + $value = $operator; + $operator = '='; + case 3: + $return = "$field $operator"; + if (isset($specials[$operator]['prefix'])) { + $return .= $specials[$operator]['prefix']; + } + if ($value instanceOf SelectQuery) { + $values = $value->values(); + // There is an implicit string cast on $value, since SelectQuery implements __toString(). + $placeholder = '('. $value .')'; + } + else { + $count = count($value); + if ($count <= 1) { // count(NULL) = 0 + $placeholder = ':db_placeholder_'. ($max_placeholder++); + $values = array($placeholder => $value); + } + elseif (isset($specials[$operator])) { + list($placeholder, $values) = $this->placeholderMultiple($count, $specials[$operator]['delimiter'], $value); + } + else { + // What are you doing here? + } + } + $return .= " $placeholder"; + if (isset($specials[$operator]['postfix'])) { + $return .= $specials[$operator]['postfix']; + } + return array($return, $values); + } + } +} + +/** + * Returns a new DatabaseCondition, set to "AND" all conditions together. + */ +function db_or() { + return new DatabaseCondition('OR'); +} + +/** + * Returns a new DatabaseCondition, set to "OR" all conditions together. + */ +function db_and() { + return new DatabaseCondition('AND'); +} + +/** + * Returns a new DatabaseCondition, set to "XOR" all conditions together. + */ +function db_xor() { + return new DatabaseCondition('XOR'); +} + +/** + * The following utility functions are simply convenience wrappers. + * They should never, ever have any database-specific code in them. + */ + +/** + * Execute an arbitrary query string against the active database. + * + * Do not use this function for INSERT, UPDATE, or DELETE queries. Those should + * be handled via the appropriate query builder factory. Use this function for + * SELECT queries that do not require a query builder. + * + * @see DatabaseConnection::defaultOptions() + * @param $query + * The prepared statement query to run. Although it will accept both + * named and unnamed placeholders, named placeholders are strongly preferred + * as they are more self-documenting. + * @param $args + * An array of values to substitute into the query. If the query uses named + * placeholders, this is an associative array in any order. If the query uses + * unnamed placeholders (?), this is an indexed array and the order must match + * the order of placeholders in the query string. + * @param $options + * An array of options to control how the query operates. + * @return + * A prepared statement object, already executed. + */ +function db_query($query, $args = array(), $options = array()) { + // Temporary backward-compatibliity hacks. Remove later. + if (!is_array($options)) { + $options = array(); + } + if (!is_array($args)) { + $args = func_get_args(); + array_shift($args); + } + $old_query = $query; + $query = str_replace(array('%d', '%f', '%b', "'%s'", '%s'), '?', $old_query); + if ($old_query !== $query) { + $args = array_values($args); // The old system allowed named arrays, but PDO doesn't if you use ?. + } + + if (empty($options['target'])) { + $options['target'] = 'default'; + } + + return Database::getActiveConnection($options['target'])->query($query, $args, $options); +} + +/** + * Execute an arbitrary query string against the active database, restricted to a specified range. + * + * @see DatabaseConnection::defaultOptions() + * @param $query + * The prepared statement query to run. Although it will accept both + * named and unnamed placeholders, named placeholders are strongly preferred + * as they are more self-documenting. + * @param $args + * An array of values to substitute into the query. If the query uses named + * placeholders, this is an associative array in any order. If the query uses + * unnamed placeholders (?), this is an indexed array and the order must match + * the order of placeholders in the query string. + * @param $from + * The first record from the result set to return. + * @param $limit + * The number of records to return from the result set. + * @param $options + * An array of options to control how the query operates. + * @return + * A prepared statement object, already executed. + */ +function db_query_range($query, $args, $from = 0, $count = 0, $options = array()) { + // Temporary backward-compatibliity hacks. Remove later. + if (!is_array($options)) { + $options = array(); + } + if (!is_array($args)) { + $args = func_get_args(); + array_shift($args); + $count = array_pop($args); + $from = array_pop($args); + } + $old_query = $query; + $query = str_replace(array('%d', '%f', '%b', "'%s'", '%s'), '?', $old_query); + if ($old_query !== $query) { + $args = array_values($args); // The old system allowed named arrays, but PDO doesn't if you use ?. + } + + if (empty($options['target'])) { + $options['target'] = 'default'; + } + + return Database::getActiveConnection($options['target'])->queryRange($query, $args, $from, $count, $options); +} + +/** + * Execute a query string against the active database and save the result set to a temp table. + * + * @see DatabaseConnection::defaultOptions() + * @param $query + * The prepared statement query to run. Although it will accept both + * named and unnamed placeholders, named placeholders are strongly preferred + * as they are more self-documenting. + * @param $args + * An array of values to substitute into the query. If the query uses named + * placeholders, this is an associative array in any order. If the query uses + * unnamed placeholders (?), this is an indexed array and the order must match + * the order of placeholders in the query string. + * @param $from + * The first record from the result set to return. + * @param $limit + * The number of records to return from the result set. + * @param $options + * An array of options to control how the query operates. + */ +function db_query_temporary($query, $args, $tablename, $options = array()) { + // Temporary backward-compatibliity hacks. Remove later. + if (!is_array($options)) { + $options = array(); + } + if (!is_array($args)) { + $args = func_get_args(); + array_shift($args); + } + $old_query = $query; + $query = str_replace(array('%d', '%f', '%b', "'%s'", '%s'), '?', $old_query); + if ($old_query !== $query) { + $args = array_values($args); // The old system allowed named arrays, but PDO doesn't if you use ?. + } + + if (empty($options['target'])) { + $options['target'] = 'default'; + } + + return Database::getActiveConnection($options['target'])->queryTemporary($query, $args, $tablename, $options); +} + +/** + * Returns a new InsertQuery object for the active database. + * + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $table + * The table into which to insert. + * @param $options + * An array of options to control how the query operates. + * @return + * A new InsertQuery object for this connection. + */ +function db_insert($query_id, $table, Array $options = array()) { + if (empty($options['target']) || $options['target'] == 'slave') { + $options['target'] = 'default'; + } + return Database::getActiveConnection($options['target'])->insert($query_id, $table, $options); +} + +/** + * Returns a new UpdateQuery object for the active database. + * + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $table + * The table to update. + * @param $options + * An array of options to control how the query operates. + * @return + * A new UpdateQuery object for this connection. + */ +function db_update($query_id, $table, Array $options = array()) { + if (empty($options['target']) || $options['target'] == 'slave') { + $options['target'] = 'default'; + } + return Database::getActiveConnection($options['target'])->update($query_id, $table, $options); +} + +/** + * Returns a new DeleteQuery object for the active database. + * + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $table + * The table from which to delete. + * @param $options + * An array of options to control how the query operates. * @return - * An array containing the keys: - * success: a boolean indicating whether the query succeeded - * query: the SQL query executed, passed through check_plain() + * A new DeleteQuery object for this connection. */ -function update_sql($sql) { - $result = db_query($sql, true); - return array('success' => $result !== FALSE, 'query' => check_plain($sql)); +function db_delete($query_id, $table, Array $options = array()) { + if (empty($options['target']) || $options['target'] == 'slave') { + $options['target'] = 'default'; + } + return Database::getActiveConnection($options['target'])->delete($query_id, $table, $options); } /** - * Append a database prefix to all tables in a query. + * Returns a new SelectQuery object for the active database. * - * 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 - * tables, allowing Drupal to coexist with other systems in the same database if - * necessary. - * - * @param $sql - * A string containing a partial or entire SQL query. + * @param $query_id + * A string containing the unique ID of this query. It will be used for + * query_alter hooks. + * @param $options + * An array of options to control how the query operates. * @return - * The properly-prefixed string. + * A new SelectQuery object for this connection. */ -function db_prefix_tables($sql) { - global $db_prefix; - - if (is_array($db_prefix)) { - if (array_key_exists('default', $db_prefix)) { - $tmp = $db_prefix; - unset($tmp['default']); - foreach ($tmp as $key => $val) { - $sql = strtr($sql, array('{'. $key .'}' => $val . $key)); - } - return strtr($sql, array('{' => $db_prefix['default'], '}' => '')); - } - else { - foreach ($db_prefix as $key => $val) { - $sql = strtr($sql, array('{'. $key .'}' => $val . $key)); - } - return strtr($sql, array('{' => '', '}' => '')); - } - } - else { - return strtr($sql, array('{' => $db_prefix, '}' => '')); +function db_select($query_id, Array $options = array()) { + if (empty($options['target'])) { + $options['target'] = 'default'; } + return Database::getActiveConnection($options['target'])->select($query_id, $options); } /** - * Activate a database for future queries. - * - * If it is necessary to use external databases in a project, this function can - * be used to change where database queries are sent. If the database has not - * yet been used, it is initialized using the URL specified for that name in - * Drupal's configuration file. If this name is not defined, a duplicate of the - * default connection is made instead. + * Sets a new active database. * - * Be sure to change the connection back to the default when done with custom - * code. + * @param $key + * The key in the $databases array to set as the default database. + * @returns + * The key of the formerly active database. + */ +function db_set_active($key = 'default') { + return Database::setActiveConnection($key); +} + +/** + * Determine if there is an active connection. * - * @param $name - * The name assigned to the newly active database connection. If omitted, the - * default connection will be made active. + * Note that this method will return FALSE if no connection has been established + * yet, even if one could be. * - * @return the name of the previously active database or FALSE if non was found. + * @return + * TRUE if there is at least one database connection established, FALSE otherwise. */ -function db_set_active($name = 'default') { - global $db_url, $db_type, $active_db; - static $db_conns, $active_name = FALSE; - - if (empty($db_url)) { - include_once 'includes/install.inc'; - install_goto('install.php'); - } - - if (!isset($db_conns[$name])) { - // Initiate a new connection, using the named DB URL specified. - if (is_array($db_url)) { - $connect_url = array_key_exists($name, $db_url) ? $db_url[$name] : $db_url['default']; - } - else { - $connect_url = $db_url; - } - - $db_type = substr($connect_url, 0, strpos($connect_url, '://')); - $handler = "./includes/database.$db_type.inc"; - - if (is_file($handler)) { - include_once $handler; - } - else { - _db_error_page("The database type '". $db_type ."' is unsupported. Please use either 'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases."); - } - - $db_conns[$name] = db_connect($connect_url); - } - - $previous_name = $active_name; - // Set the active connection. - $active_name = $name; - $active_db = $db_conns[$name]; - - return $previous_name; +function db_is_active() { + return Database::isActiveConnection(); } /** - * Helper function to show fatal database errors. + * Restrict a dynamic table, column or constraint name to safe characters. * - * Prints a themed maintenance page with the 'Site off-line' text, - * adding the provided error message in the case of 'display_errors' - * set to on. Ends the page request; no return. + * Only keeps alphanumeric and underscores. * - * @param $error - * The error message to be appended if 'display_errors' is on. + * @param $string + * The table name to escape. + * @return + * The escaped table name as a string. */ -function _db_error_page($error = '') { - global $db_type; - drupal_maintenance_theme(); - drupal_set_header('HTTP/1.1 503 Service Unavailable'); - drupal_set_title('Site off-line'); - - $message = '

    The site is currently not available due to technical problems. Please try again later. Thank you for your understanding.

    '; - $message .= '

    If you are the maintainer of this site, please check your database settings in the settings.php file and ensure that your hosting provider\'s database server is running. For more help, see the handbook, or contact your hosting provider.

    '; - - if ($error && ini_get('display_errors')) { - $message .= '

    The '. theme('placeholder', $db_type) .' error was: '. theme('placeholder', $error) .'.

    '; - } +function db_escape_table($string) { + return Database::getActiveConnection()->escapeTable($table); +} - print theme('maintenance_page', $message); - exit; +/** + * Perform an SQL query and return success or failure. + * + * @param $sql + * A string containing a complete SQL query. %-substitution + * parameters are not supported. + * @return + * An array containing the keys: + * success: a boolean indicating whether the query succeeded + * query: the SQL query executed, passed through check_plain() + */ +function update_sql($sql) { + $result = Database::getActiveConnection()->query($sql, array(true)); + return array('success' => $result !== FALSE, 'query' => check_plain($sql)); } /** - * Returns a boolean depending on the availability of the database. + * Returns the last insert id. + * + * @todo Remove this function when all queries have been ported to db_insert(). + * @param $table + * The name of the table you inserted into. + * @param $field + * The name of the autoincrement field. */ -function db_is_active() { - global $active_db; - return !empty($active_db); +function db_last_insert_id($table, $field) { + return Database::getActiveConnection()->lastInsertId(); } /** - * Helper function for db_query(). + * Determine the number of rows changed by the preceding query. + * + * This may not work, actually, without some tricky temp code. + * + * @todo Remove this function when all queries have been ported to db_update(). */ -function _db_query_callback($match, $init = FALSE) { - static $args = NULL; - if ($init) { - $args = $match; - return; - } - - switch ($match[1]) { - case '%d': // We must use type casting to int to convert FALSE/NULL/(TRUE?) - return (int) array_shift($args); // We don't need db_escape_string as numbers are db-safe - case '%s': - return db_escape_string(array_shift($args)); - case '%%': - return '%'; - case '%f': - return (float) array_shift($args); - case '%b': // binary data - return db_encode_blob(array_shift($args)); +function db_affected_rows() { + $statement = Database::getActiveConnection()->lastStatement; + if (!$statement) { + return 0; } + return $statement->rowCount(); } /** @@ -225,6 +2103,7 @@ function _db_query_callback($match, $ini * Given a Schema API field type, return correct %-placeholders to * embed in a query * + * @todo This may be possible to remove in favor of db_select(). * @param $arguments * An array with at least one element. * @param $type @@ -236,16 +2115,12 @@ function db_placeholders($arguments, $ty } /** - * Indicates the place holders that should be replaced in _db_query_callback(). - */ -define('DB_QUERY_REGEXP', '/(%d|%s|%%|%f|%b)/'); - -/** * Helper function for db_rewrite_sql. * * Collects JOIN and WHERE statements via hook_db_rewrite_sql() * Decides whether to select primary_key or DISTINCT(primary_key) * + * @todo Remove this function when all code has been converted to query_alter. * @param $query * Query to be rewritten. * @param $primary_table @@ -292,6 +2167,7 @@ function _db_rewrite_sql($query = '', $p * Rewrites node, taxonomy and comment queries. Use it for listing queries. Do not * use FROM table1, table2 syntax, use JOIN instead. * + * @todo Remove this function when all code has been converted to query_alter. * @param $query * Query to be rewritten. * @param $primary_table @@ -360,129 +2236,34 @@ function db_rewrite_sql($query, $primary } /** - * Restrict a dynamic table, column or constraint name to safe characters. - * - * Only keeps alphanumeric and underscores. + * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to + * the SELECT list entry of the given query and the resulting query is returned. + * This function only applies the wrapper if a DISTINCT doesn't already exist in + * the query. + * + * @todo Remove this. + * @param $table Table containing the field to set as DISTINCT + * @param $field Field to set as DISTINCT + * @param $query Query to apply the wrapper to + * @return SQL query with the DISTINCT wrapper surrounding the given table.field. */ -function db_escape_table($string) { - return preg_replace('/[^A-Za-z0-9_]+/', '', $string); +function db_distinct_field($table, $field, $query) { + return Database::getActiveConnection()->distinctField($table, $field, $query); } + /** * @} End of "defgroup database". */ + /** - * @defgroup schemaapi Schema API + * @ingroup schemaapi * @{ - * - * A Drupal schema definition is an array structure representing one or - * more tables and their related keys and indexes. A schema is defined by - * hook_schema(), which usually lives in a modulename.install file. - * - * By implementing hook_schema() and specifying the tables your module - * declares, you can easily create and drop these tables on all - * supported database engines. You don't have to deal with the - * different SQL dialects for table creation and alteration of the - * supported database engines. - * - * hook_schema() should return an array with a key for each table that - * the module defines. - * - * The following keys are defined: - * - * - 'description': A string describing this table and its purpose. - * References to other tables should be enclosed in - * curly-brackets. For example, the node_revisions table - * description field might contain "Stores per-revision title and - * body data for each {node}." - * - 'fields': An associative array ('fieldname' => specification) - * that describes the table's database columns. The specification - * is also an array. The following specification parameters are defined: - * - * - 'description': A string describing this field and its purpose. - * References to other tables should be enclosed in - * curly-brackets. For example, the node table vid field - * description might contain "Always holds the largest (most - * recent) {node_revisions}.vid value for this nid." - * - 'type': The generic datatype: 'varchar', 'int', 'serial' - * 'float', 'numeric', 'text', 'blob' or 'datetime'. Most types - * just map to the according database engine specific - * datatypes. Use 'serial' for auto incrementing fields. This - * will expand to 'int auto_increment' on mysql. - * - 'size': The data size: 'tiny', 'small', 'medium', 'normal', - * 'big'. This is a hint about the largest value the field will - * store and determines which of the database engine specific - * datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT). - * 'normal', the default, selects the base type (e.g. on MySQL, - * INT, VARCHAR, BLOB, etc.). - * - * Not all sizes are available for all data types. See - * db_type_map() for possible combinations. - * - 'not null': If true, no NULL values will be allowed in this - * database column. Defaults to false. - * - 'default': The field's default value. The PHP type of the - * value matters: '', '0', and 0 are all different. If you - * specify '0' as the default value for a type 'int' field it - * will not work because '0' is a string containing the - * character "zero", not an integer. - * - 'length': The maximal length of a type 'varchar' or 'text' - * field. Ignored for other field types. - * - 'unsigned': A boolean indicating whether a type 'int', 'float' - * and 'numeric' only is signed or unsigned. Defaults to - * FALSE. Ignored for other field types. - * - 'precision', 'scale': For type 'numeric' fields, indicates - * the precision (total number of significant digits) and scale - * (decimal digits right of the decimal point). Both values are - * mandatory. Ignored for other field types. - * - * All parameters apart from 'type' are optional except that type - * 'numeric' columns must specify 'precision' and 'scale'. - * - * - 'primary key': An array of one or more key column specifiers (see below) - * that form the primary key. - * - 'unique key': An associative array of unique keys ('keyname' => - * specification). Each specification is an array of one or more - * key column specifiers (see below) that form a unique key on the table. - * - 'indexes': An associative array of indexes ('indexame' => - * specification). Each specification is an array of one or more - * key column specifiers (see below) that form an index on the - * table. - * - * A key column specifier is either a string naming a column or an - * array of two elements, column name and length, specifying a prefix - * of the named column. - * - * As an example, here is a SUBSET of the schema definition for - * Drupal's 'node' table. It show four fields (nid, vid, type, and - * title), the primary key on field 'nid', a unique key named 'vid' on - * field 'vid', and two indexes, one named 'nid' on field 'nid' and - * one named 'node_title_type' on the field 'title' and the first four - * bytes of the field 'type': - * - * @code - * $schema['node'] = array( - * 'fields' => array( - * 'nid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), - * 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0), - * 'type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), - * 'title' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''), - * ), - * 'primary key' => array('nid'), - * 'unique keys' => array( - * 'vid' => array('vid') - * ), - * 'indexes' => array( - * 'nid' => array('nid'), - * 'node_title_type' => array('title', array('type', 4)), - * ), - * ); - * @endcode - * - * @see drupal_install_schema() */ - /** + +/** * Create a new table from a Drupal table definition. * * @param $ret @@ -493,10 +2274,7 @@ function db_escape_table($string) { * A Schema API table definition array. */ function db_create_table(&$ret, $name, $table) { - $statements = db_create_table_sql($name, $table); - foreach ($statements as $statement) { - $ret[] = update_sql($statement); - } + return Database::getActiveConnection()->schema()->createTable($ret, $name, $table); } /** @@ -511,24 +2289,31 @@ function db_create_table(&$ret, $name, $ * An array of field names. */ function db_field_names($fields) { - $ret = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $ret[] = $field[0]; - } - else { - $ret[] = $field; - } - } - return $ret; + return Database::getActiveConnection()->schema()->fieldNames($fields); +} + +/** + * Check if a table exists. + */ +function db_table_exists($table) { + return Database::getActiveConnection()->schema()->tableExists($table); +} + +/** + * Check if a column exists in the given table. + */ +function db_column_exists($table, $column) { + return Database::getActiveConnection()->schema()->columnExists($table, $column); } + /** * Given a Schema API field type, return the correct %-placeholder. * * Embed the placeholder in a query to be passed to db_query and and pass as an * argument to db_query a value of the specified type. * + * @todo Remove this after all queries are converted to type-agnostic form. * @param $type * The Schema API type of a field. * @return @@ -567,6 +2352,302 @@ function db_type_placeholder($type) { return 'unsupported type '. $type .'for db_type_placeholder'; } + +function _db_create_keys_sql($spec) { + return Database::getActiveConnection()->schema()->createKeysSql($spec); +} + +/** + * This maps a generic data type in combination with its data size + * to the engine-specific data type. + */ +function db_type_map() { + return Database::getActiveConnection()->schema()->getFieldTypeMap(); +} + +/** + * Rename a table. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be renamed. + * @param $new_name + * The new name for the table. + */ +function db_rename_table(&$ret, $table, $new_name) { + return Database::getActiveConnection()->schema()->renameTable($ret, $table, $new_name); +} + +/** + * Drop a table. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be dropped. + */ +function db_drop_table(&$ret, $table) { + return Database::getActiveConnection()->schema()->dropTable($ret, $table); +} + +/** + * Add a new field to a table. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * Name of the table to be altered. + * @param $field + * Name of the field to be added. + * @param $spec + * The field specification array, as taken from a schema definition. + * The specification may also contain the key 'initial', the newly + * created field will be set to the value of the key in all rows. + * This is most useful for creating NOT NULL columns with no default + * value in existing tables. + * @param $keys_new + * Optional keys and indexes specification to be created on the + * table along with adding the field. The format is the same as a + * table specification but without the 'fields' element. If you are + * adding a type 'serial' field, you MUST specify at least one key + * or index including it in this array. @see db_change_field for more + * explanation why. + */ +function db_add_field(&$ret, $table, $field, $spec, $keys_new = array()) { + return Database::getActiveConnection()->schema()->addField($ret, $table, $field, $spec, $keys_new); +} + +/** + * Drop a field. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $field + * The field to be dropped. + */ +function db_drop_field(&$ret, $table, $field) { + return Database::getActiveConnection()->schema()->dropField($ret, $table, $field); +} + +/** + * Set the default value for a field. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $field + * The field to be altered. + * @param $default + * Default value to be set. NULL for 'default NULL'. + */ +function db_field_set_default(&$ret, $table, $field, $default) { + return Database::getActiveConnection()->schema()->dropField($ret, $table, $field, $default); +} + +/** + * Set a field to have no default value. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $field + * The field to be altered. + */ +function db_field_set_no_default(&$ret, $table, $field) { + return Database::getActiveConnection()->schema()->fieldSetNoDefault($ret, $table, $field); +} + +/** + * Add a primary key. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $fields + * Fields for the primary key. + */ +function db_add_primary_key(&$ret, $table, $fields) { + return Database::getActiveConnection()->schema()->addPrimaryKey($ret, $table, $field); +} + +/** + * Drop the primary key. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + */ +function db_drop_primary_key(&$ret, $table) { + return Database::getActiveConnection()->schema()->dropPrimaryKey($ret, $table); +} + +/** + * Add a unique key. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $name + * The name of the key. + * @param $fields + * An array of field names. + */ +function db_add_unique_key(&$ret, $table, $name, $fields) { + return Database::getActiveConnection()->schema()->addUniqueKey($ret, $table, $name, $fields); +} + +/** + * Drop a unique key. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $name + * The name of the key. + */ +function db_drop_unique_key(&$ret, $table, $name) { + return Database::getActiveConnection()->schema()->dropUniqueKey($ret, $table, $name); +} + +/** + * Add an index. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $name + * The name of the index. + * @param $fields + * An array of field names. + */ +function db_add_index(&$ret, $table, $name, $fields) { + return Database::getActiveConnection()->schema()->addIndex($ret, $table, $name, $fields); +} + +/** + * Drop an index. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * The table to be altered. + * @param $name + * The name of the index. + */ +function db_drop_index(&$ret, $table, $name) { + return Database::getActiveConnection()->schema()->addIndex($ret, $table, $name); +} + +/** + * Change a field definition. + * + * IMPORTANT NOTE: To maintain database portability, you have to explicitly + * recreate all indices and primary keys that are using the changed field. + * + * That means that you have to drop all affected keys and indexes with + * db_drop_{primary_key,unique_key,index}() before calling db_change_field(). + * To recreate the keys and indices, pass the key definitions as the + * optional $keys_new argument directly to db_change_field(). + * + * For example, suppose you have: + * @code + * $schema['foo'] = array( + * 'fields' => array( + * 'bar' => array('type' => 'int', 'not null' => TRUE) + * ), + * 'primary key' => array('bar') + * ); + * @endcode + * and you want to change foo.bar to be type serial, leaving it as the + * primary key. The correct sequence is: + * @code + * db_drop_primary_key($ret, 'foo'); + * db_change_field($ret, 'foo', 'bar', 'bar', + * array('type' => 'serial', 'not null' => TRUE), + * array('primary key' => array('bar'))); + * @endcode + * + * The reasons for this are due to the different database engines: + * + * On PostgreSQL, changing a field definition involves adding a new field + * and dropping an old one which* causes any indices, primary keys and + * sequences (from serial-type fields) that use the changed field to be dropped. + * + * On MySQL, all type 'serial' fields must be part of at least one key + * or index as soon as they are created. You cannot use + * db_add_{primary_key,unique_key,index}() for this purpose because + * the ALTER TABLE command will fail to add the column without a key + * or index specification. The solution is to use the optional + * $keys_new argument to create the key or index at the same time as + * field. + * + * You could use db_add_{primary_key,unique_key,index}() in all cases + * unless you are converting a field to be type serial. You can use + * the $keys_new argument in all cases. + * + * @param $ret + * Array to which query results will be added. + * @param $table + * Name of the table. + * @param $field + * Name of the field to change. + * @param $field_new + * New name for the field (set to the same as $field if you don't want to change the name). + * @param $spec + * The field specification for the new field. + * @param $keys_new + * Optional keys and indexes specification to be created on the + * table along with changing the field. The format is the same as a + * table specification but without the 'fields' element. + */ + +function db_change_field(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) { + return Database::getActiveConnection()->schema()->changeField($ret, $table, $field, $field_new, $spec, $keys_new); +} + +/** + * @} End of "ingroup schemaapi". + */ + +/** + * @ingroup database-legacy + * + * These functions are no longer necessary, as the DatabaseStatement object + * offers this and much more functionality. They are kept temporarily for backward + * compatibility during conversion and should be removed as soon as possible. + * + * @{ + */ + +function db_fetch_object(DatabaseStatement $statement) { + return $statement->fetch(PDO::FETCH_OBJ); +} + +function db_fetch_array(DatabaseStatement $statement) { + return $statement->fetch(PDO::FETCH_ASSOC); +} + +function db_result(DatabaseStatement $statement) { + return $statement->fetchOne(); +} + +function _db_need_install() { + if (!function_exists('install_goto')) { + include_once 'includes/install.inc'; + install_goto('install.php'); + } +} + /** - * @} End of "defgroup schemaapi". + * @} End of "ingroup database-legacy". */ === modified file 'includes/database.mysql.inc' --- includes/database.mysql.inc 2008-02-17 19:39:11 +0000 +++ includes/database.mysql.inc 2008-03-01 04:05:55 +0000 @@ -11,360 +11,135 @@ * @{ */ -// Include functions shared between mysql and mysqli. -require_once './includes/database.mysql-common.inc'; +class DatabaseConnection_mysql extends DatabaseConnection { -/** - * Report database status. - */ -function db_status_report($phase) { - $t = get_t(); + public static $rand = 'RAND()'; + public static $timestamp = 'Y-m-d h:i:s'; + + protected $transactionSupport; - $version = db_version(); + public function __construct(Array $connection_options = array()) { - $form['mysql'] = array( - 'title' => $t('MySQL database'), - 'value' => ($phase == 'runtime') ? l($version, 'admin/reports/status/sql') : $version, - ); + $connection_options += array( + 'transactions' => FALSE, + 'port' => 3306, + ); + $this->transactionSupport = $connection_options['transactions']; - if (version_compare($version, DRUPAL_MINIMUM_MYSQL) < 0) { - $form['mysql']['severity'] = REQUIREMENT_ERROR; - $form['mysql']['description'] = $t('Your MySQL Server is too old. Drupal requires at least MySQL %version.', array('%version' => DRUPAL_MINIMUM_MYSQL)); + $dsn = 'mysql:host='. $connection_options['host'] .';port='. $connection_options['port'] .';dbname='. $connection_options['database']; + parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( + PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE , // So we don't have to mess around with cursors. + PDO::ATTR_EMULATE_PREPARES => TRUE,// Because MySQL's prepared statements skip the query cache, because it's dumb. + )); } - return $form; -} + public function queryRange($query, Array $args, $from, $count, Array $options) { + // Backward compatibility hack, temporary. + $query = str_replace(array('%d' , '%f' , '%b' , "'%s'"), '?', $query); -/** - * Returns the version of the database server currently in use. - * - * @return Database server version - */ -function db_version() { - list($version) = explode('-', mysql_get_server_info()); - return $version; -} + return $this->runQuery($query . ' LIMIT ' . $from . ', ' . $count, $args, $options); + } -/** - * Initialize a database connection. - */ -function db_connect($url) { - $url = parse_url($url); + public function queryTemporary($query, Array $args, $tablename) { + $query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE ' . $tablename . ' Engine=HEAP SELECT', $this->prefixTables($query)); - // Check if MySQL support is present in PHP - if (!function_exists('mysql_connect')) { - _db_error_page('Unable to use the MySQL database because the MySQL extension for PHP is not installed. Check your php.ini to see how you can enable it.'); - } - - // Decode url-encoded information in the db connection string - $url['user'] = urldecode($url['user']); - // Test if database url has a password. - $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : ''; - $url['host'] = urldecode($url['host']); - $url['path'] = urldecode($url['path']); - - // Allow for non-standard MySQL port. - if (isset($url['port'])) { - $url['host'] = $url['host'] .':'. $url['port']; - } - - // - TRUE makes mysql_connect() always open a new link, even if - // mysql_connect() was called before with the same parameters. - // This is important if you are using two databases on the same - // server. - // - 2 means CLIENT_FOUND_ROWS: return the number of found - // (matched) rows, not the number of affected rows. - $connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2); - if (!$connection || !mysql_select_db(substr($url['path'], 1))) { - // Show error screen otherwise - _db_error_page(mysql_error()); - } - // Require ANSI mode to improve SQL portability. - mysql_query("SET SESSION sql_mode='ANSI'", $connection); - // Force UTF-8. - mysql_query('SET NAMES "utf8"', $connection); - return $connection; -} + return $this->runQuery($query, $args, $options); + } -/** - * Helper function for db_query(). - */ -function _db_query($query, $debug = 0) { - global $active_db, $queries, $user; + public function driver() { + return 'mysql'; + } - if (variable_get('dev_query', 0)) { - list($usec, $sec) = explode(' ', microtime()); - $timer = (float)$usec + (float)$sec; - // If devel.module query logging is enabled, prepend a comment with the username and calling function - // to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact - // code is issueing the slow query. - $bt = debug_backtrace(); - // t() may not be available yet so we don't wrap 'Anonymous'. - $name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous'); - // str_replace() to prevent SQL injection via username or anonymous name. - $name = str_replace(array('*', '/'), '', $name); - $query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query; - } - - $result = mysql_query($query, $active_db); - - if (variable_get('dev_query', 0)) { - $query = $bt[2]['function'] ."\n". $query; - list($usec, $sec) = explode(' ', microtime()); - $stop = (float)$usec + (float)$sec; - $diff = $stop - $timer; - $queries[] = array($query, $diff); - } - - if ($debug) { - print '

    query: '. $query .'
    error:'. mysql_error($active_db) .'

    '; - } - - if (!mysql_errno($active_db)) { - return $result; - } - else { - // Indicate to drupal_error_handler that this is a database error. - ${DB_ERROR} = TRUE; - trigger_error(check_plain(mysql_error($active_db) ."\nquery: ". $query), E_USER_WARNING); - return FALSE; + public function databaseType() { + return 'mysql'; } -} -/** - * Fetch one result row from the previous query as an object. - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * An object representing the next row of the result, or FALSE. The attributes - * of this object are the table fields selected by the query. - */ -function db_fetch_object($result) { - if ($result) { - return mysql_fetch_object($result); + public function supportsTransactions() { + return $this->transactionSupport; } -} -/** - * Fetch one result row from the previous query as an array. - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * An associative array representing the next row of the result, or FALSE. - * The keys of this object are the names of the table fields selected by the - * query, and the values are the field values for this result row. - */ -function db_fetch_array($result) { - if ($result) { - return mysql_fetch_array($result, MYSQL_ASSOC); + public function escapeTable($table) { + return preg_replace('/[^A-Za-z0-9_]+/', '', $table); } -} -/** - * Return an individual result field from the previous query. - * - * Only use this function if exactly one field is being selected; otherwise, - * use db_fetch_object() or db_fetch_array(). - * - * @param $result - * A database query result resource, as returned from db_query(). - * @return - * The resulting field or FALSE. - */ -function db_result($result) { - if ($result && mysql_num_rows($result) > 0) { - // The mysql_fetch_row function has an optional second parameter $row - // but that can't be used for compatibility with Oracle, DB2, etc. - $array = mysql_fetch_row($result); - return $array[0]; + /** + * @todo Remove this as soon as db_rewrite_sql() has been exterminated. + */ + public function distinctField($table, $field, $query) { + $field_to_select = 'DISTINCT('. $table .'.'. $field .')'; + // (?queryId, $this); -/** - * Returns text from a Binary Large Object value. - * - * @param $data - * Data to decode. - * @return - * Decoded data. - */ -function db_decode_blob($data) { - return $data; -} + $max_placeholder = 0; + foreach ($this->insertValues as $insert_values) { + foreach ($insert_values as $value) { + $values[':db_insert_placeholder_'. $max_placeholder++] = $value; + } + } -/** - * Prepare user input for use in a database query, preventing SQL injection attacks. - */ -function db_escape_string($text) { - global $active_db; - return mysql_real_escape_string($text, $active_db); -} + if ($this->updateValues) { + $max_placeholder = 0; + foreach ($this->updateValues as $value) { + $values[':db_update_placeholder_'. $max_placeholder++] = $value; + } + } -/** - * Lock a table. - */ -function db_lock_table($table) { - db_query('LOCK TABLES {'. db_escape_table($table) .'} WRITE'); -} + $num_affected = $this->connection->runQuery((string)$this, $values, $this->queryOptions); -/** - * Unlock all locked tables. - */ -function db_unlock_tables() { - db_query('UNLOCK TABLES'); -} + return $this->connection->lastInsertId(); + } -/** - * Check if a table exists. - */ -function db_table_exists($table) { - return (bool) db_fetch_object(db_query("SHOW TABLES LIKE '{". db_escape_table($table) ."}'")); -} + public function __toString() { -/** - * Check if a column exists in the given table. - */ -function db_column_exists($table, $column) { - return (bool) db_fetch_object(db_query("SHOW COLUMNS FROM {". db_escape_table($table) ."} LIKE '". db_escape_table($column) ."'")); + $delay = $this->queryOptions['delay'] ? ' DELAYED ' : ''; + $query = "INSERT $delay INTO {" . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES '; + + $max_placeholder = 0; + $values = array(); + foreach ($this->insertValues as $insert_values) { + $placeholders = array(); + $new_placeholder = $max_placeholder + count($insert_values); + for ($i = $max_placeholder; $i < $new_placeholder; ++$i) { + $placeholders[] = ':db_insert_placeholder_'. $i; + } + $max_placeholder = $new_placeholder; + $values[] = '('. implode(', ', $placeholders) .')'; + } + + $query .= implode(', ', $values); + + if ($this->updateFields) { + $query .= ' ON DUPLICATE KEY UPDATE '. implode(', ', $this->updateFields); + } + + return $query; + + } } -/** - * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to - * the SELECT list entry of the given query and the resulting query is returned. - * This function only applies the wrapper if a DISTINCT doesn't already exist in - * the query. - * - * @param $table Table containing the field to set as DISTINCT - * @param $field Field to set as DISTINCT - * @param $query Query to apply the wrapper to - * @return SQL query with the DISTINCT wrapper surrounding the given table.field. - */ -function db_distinct_field($table, $field, $query) { - $field_to_select = 'DISTINCT('. $table .'.'. $field .')'; - // (? $elements['#title']))); } === modified file 'includes/install.inc' --- includes/install.inc 2008-02-12 13:45:16 +0000 +++ includes/install.inc 2008-02-24 16:00:17 +0000 @@ -151,19 +151,112 @@ function drupal_detect_baseurl($file = ' function drupal_detect_database_types() { $databases = array(); - foreach (array('mysql', 'mysqli', 'pgsql') as $type) { - if (file_exists('./includes/install.'. $type .'.inc')) { - include_once './includes/install.'. $type .'.inc'; - $function = $type .'_is_available'; - if ($function()) { - $databases[$type] = $type; - } + foreach (glob('./includes/database.*.inc') as $driver_file) { + list(, $driver) = explode('.', basename($driver_file)); + $drivers[] = $driver; + include_once $driver_file; + $install_file = './includes/install.'. $driver .'.inc'; + if (file_exists($install_file)) { + include_once $install_file; + } + $class = 'DatabaseInstaller_'. $driver; + $installer = new $class(); + if ($installer->installable()) { + $databases[$driver] = $installer->name(); } } return $databases; } +abstract class DatabaseInstaller { + protected $success = array(); + protected $tests = array( + 'testCreate' => array( + 'query' => 'CREATE TABLE drupal_install_test (id int NULL)', + 'success' => 'CREATE', + 'message' => 'Failed to create a test table on your %name database server with the command %query. %name reports the following message: %error.For more help, see the Installation and upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.', + 'fatal' => TRUE, + ), + 'testInsert' => array( + 'query' => 'INSERT INTO drupal_install_test (id) VALUES (1)', + 'success' => 'INSERT', + 'message' => 'Failed to insert a value into a test table on your %name database server. We tried inserting a value with the command %query and %name reported the following error: %error.', + ), + 'testUpdate' => array( + 'query' => 'UPDATE drupal_install_test SET id = 2', + 'success' => 'UPDATE', + 'message' => 'Failed to update a value in a test table on your %name database server. We tried updating a value with the command %query and %name reported the following error: %error.', + ), + 'testDelete' => array( + 'query' => 'DELETE FROM drupal_install_test', + 'success' => 'DELETE', + 'message' => 'Failed to delete a value from a test table on your %name database server. We tried deleting a value with the command %query and %name reported the following error: %error.', + ), + 'testDrop' => array( + 'query' => 'DROP TABLE drupal_install_test', + 'success' => 'DELETE', + 'message' => 'Failed to drop a test table from your %name database server. We tried dropping a table with the command %query and %name reported the following error %error.', + ), + ); + public $error = FALSE; + + protected function hasPdoDriver() { + return in_array($this->pdoDriver, PDO::getAvailableDrivers()); + } + + public function installable() { + return $this->hasPdoDriver(); + } + + abstract public function name(); + + public function test() { + $return = $this->testConnect(); + if ($return === FALSE) { + return FALSE; + } + foreach ($this->tests as $test) { + $return = $this->runTestQuery($test['query'], $test['success'], $test['message'], !empty($tests['fatal'])); + if ($return === FALSE) { + return FALSE; + } + } + return $this->success; + } + + /** + * Check if we can connect to the database. + * + * @return + * FALSE on failure. + */ + protected function testConnect() { + try { + db_set_active(); + $this->success[] = 'CONNECT'; + } + catch (Exception $e) { + drupal_set_message(st('Failed to connect to your %name database server. %name reports the following message: %error.For more help, see the Installation and upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => $e->getMessage(), 'name' => $this->name())), 'error'); + return FALSE; + } + } + + protected function runTestQuery($query, $success, $message, $fatal = FALSE) { + try { + db_query($query); + $this->success[] = $success; + } + catch (Exception $e) { + drupal_set_message(st($message, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name())), 'error'); + $this->error = TRUE; + if ($fatal) { + return FALSE; + } + } + } +} + /** * Read settings.php into a buffer line by line, changing values specified in * $settings array, then over-writing the old settings.php file. @@ -212,7 +305,7 @@ function drupal_rewrite_settings($settin // Write new value to settings.php in the following format: // $'setting' = 'value'; // 'comment' $setting = $settings[$variable[1]]; - $buffer .= '$'. $variable[1] ." = '". $setting['value'] ."';". (!empty($setting['comment']) ? ' // '. $setting['comment'] ."\n" : "\n"); + $buffer .= '$'. $variable[1] ." = ". var_export($setting['value'], TRUE) .";". (!empty($setting['comment']) ? ' // '. $setting['comment'] ."\n" : "\n"); unset($settings[$variable[1]]); } else { @@ -228,7 +321,7 @@ function drupal_rewrite_settings($settin // Add required settings that were missing from settings.php. foreach ($settings as $setting => $data) { if ($data['required']) { - $buffer .= "\$$setting = '". $data['value'] ."';\n"; + $buffer .= "\$$setting = ". var_export($data['value'], TRUE) .";\n"; } } === modified file 'includes/locale.inc' --- includes/locale.inc 2008-01-09 21:36:13 +0000 +++ includes/locale.inc 2008-03-01 02:38:39 +0000 @@ -1920,34 +1920,34 @@ function _locale_translate_seek() { // Compute LIKE section switch ($query['translation']) { case 'translated': - $where = "WHERE (t.translation LIKE '%%%s%%')"; + $where = "WHERE (t.translation LIKE ?)"; $orderby = "ORDER BY t.translation"; - $arguments[] = $query['string']; + $arguments[] = '%'. $query['string'] .'%'; break; case 'untranslated': - $where = "WHERE (s.source LIKE '%%%s%%' AND t.translation IS NULL)"; + $where = "WHERE (s.source LIKE ? AND t.translation IS NULL)"; $orderby = "ORDER BY s.source"; - $arguments[] = $query['string']; + $arguments[] = '%'. $query['string'] .'%'; break; case 'all' : default: - $where = "WHERE (s.source LIKE '%%%s%%' OR t.translation LIKE '%%%s%%')"; + $where = "WHERE (s.source LIKE ? OR t.translation LIKE ?)"; $orderby = ''; - $arguments[] = $query['string']; - $arguments[] = $query['string']; + $arguments[] = '%'. $query['string'] .'%'; + $arguments[] = '%'. $query['string'] .'%'; break; } $grouplimit = ''; if (!empty($query['group']) && $query['group'] != 'all') { - $grouplimit = " AND s.textgroup = '%s'"; + $grouplimit = " AND s.textgroup = ?"; $arguments[] = $query['group']; } switch ($query['language']) { // Force search in source strings case "en": - $sql = $join ." WHERE s.source LIKE '%%%s%%' $grouplimit ORDER BY s.source"; - $arguments = array($query['string']); // $where is not used, discard its arguments + $sql = $join ." WHERE s.source LIKE ? $grouplimit ORDER BY s.source"; + $arguments = array('%'. $query['string'] .'%'); // $where is not used, discard its arguments if (!empty($grouplimit)) { $arguments[] = $query['group']; } @@ -1958,7 +1958,7 @@ function _locale_translate_seek() { break; // Some different language default: - $sql = "$join AND t.language = '%s' $where $grouplimit $orderby"; + $sql = "$join AND t.language = ? $where $grouplimit $orderby"; array_unshift($arguments, $query['language']); // Don't show translation flags for other languages, we can't see them with this search. $limit_language = $query['language']; @@ -2068,7 +2068,12 @@ function _locale_rebuild_js($langcode = // Construct the array for JavaScript translations. // We sort on plural so that we have all plural forms before singular forms. - $result = db_query("SELECT s.lid, s.source, t.plid, t.plural, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.location LIKE '%%.js%%' AND s.textgroup = 'default' ORDER BY t.plural DESC", $language->language); + $result = db_query("SELECT s.lid, s.source, t.plid, t.plural, t.translation + FROM {locales_source} s + LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language + WHERE s.location LIKE '%.js%' + AND s.textgroup = 'default' + ORDER BY t.plural DESC", array(':language' => $language->language)); $translations = $plurals = array(); while ($data = db_fetch_object($result)) { === modified file 'includes/menu.inc' --- includes/menu.inc 2008-02-23 08:13:09 +0000 +++ includes/menu.inc 2008-02-25 05:39:45 +0000 @@ -2119,9 +2119,21 @@ function _menu_update_parental_status($i // If plid == 0, there is nothing to update. if ($item['plid']) { // We may want to exclude the passed link as a possible child. - $where = $exclude ? " AND mlid != %d" : ''; + //$where = $exclude ? " AND mlid != %d" : ''; // Check if at least one visible child exists in the table. - $parent_has_children = (bool)db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND plid = %d AND hidden = 0". $where, $item['menu_name'], $item['plid'], $item['mlid'], 0, 1)); + $query = db_select('menu_update_parental_status') + ->fields('mlid') + ->join('menu_links', 'm') + ->condition('menu_name', $item['menu_name']) + ->condition('plid', $item['plid']) + ->limit(0, 1); + + if ($exclude) { + $query->condition('mlid', '!=', $item['mlid']); + } + + $parent_has_children = (bool) $query->execute()->fetchOne(); + //$parent_has_children = (bool)db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE menu_name = '%s' AND plid = %d AND hidden = 0". $where, $item['menu_name'], $item['plid'], $item['mlid'], 0, 1)); db_query("UPDATE {menu_links} SET has_children = %d WHERE mlid = %d", $parent_has_children, $item['plid']); } } === modified file 'install.php' --- install.php 2008-02-10 19:03:47 +0000 +++ install.php 2008-02-27 04:48:36 +0000 @@ -55,7 +55,7 @@ function install_main() { // Establish a connection to the database. require_once './includes/database.inc'; - db_set_active(); + //db_set_active(); // Check if Drupal is installed. $task = install_verify_drupal(); @@ -136,9 +136,12 @@ function install_main() { */ function install_verify_drupal() { // Read the variable manually using the @ so we don't trigger an error if it fails. - $result = @db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task'); - if ($result) { - return unserialize(db_result($result)); + try { + if ($result = db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task')) { + return unserialize(db_result($result)); + } + } + catch (Exception $e) { } } @@ -146,23 +149,18 @@ function install_verify_drupal() { * Verify existing settings.php */ function install_verify_settings() { - global $db_prefix, $db_type, $db_url; + global $db_prefix, $databases; // Verify existing settings (if any). - if (!empty($db_url)) { + if (!empty($databases)) { // We need this because we want to run form_get_errors. include_once './includes/form.inc'; - $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url); - $db_user = urldecode($url['user']); - $db_pass = isset($url['pass']) ? urldecode($url['pass']) : NULL; - $db_host = urldecode($url['host']); - $db_port = isset($url['port']) ? urldecode($url['port']) : ''; - $db_path = ltrim(urldecode($url['path']), '/'); + $database = $databases['default']['default']; $settings_file = './'. conf_path(FALSE, TRUE) .'/settings.php'; $form_state = array(); - _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_port, $db_path, $settings_file, $form_state); + _install_settings_form_validate($database, $settings_file, $form_state); if (!form_get_errors()) { return TRUE; } @@ -174,30 +172,23 @@ function install_verify_settings() { * Configure and rewrite settings.php. */ function install_change_settings($profile = 'default', $install_locale = '') { - global $db_url, $db_type, $db_prefix; + global $databases, $db_prefix; - $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url); - $db_user = isset($url['user']) ? urldecode($url['user']) : ''; - $db_pass = isset($url['pass']) ? urldecode($url['pass']) : ''; - $db_host = isset($url['host']) ? urldecode($url['host']) : ''; - $db_port = isset($url['port']) ? urldecode($url['port']) : ''; - $db_path = ltrim(urldecode($url['path']), '/'); $conf_path = './'. conf_path(FALSE, TRUE); $settings_file = $conf_path .'/settings.php'; - + $database = $databases['default']['default']; // We always need this because we want to run form_get_errors. include_once './includes/form.inc'; install_task_list('database'); - if ($db_url == 'mysql://username:password@localhost/databasename') { - $db_user = $db_pass = $db_path = ''; - } - elseif (!empty($db_url)) { +/* + if (isset($database['username'])) { // Do not install over a configured settings.php. install_already_done_error(); } +*/ - $output = drupal_get_form('install_settings_form', $profile, $install_locale, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host, $db_port, $db_path); + $output = drupal_get_form('install_settings_form', $profile, $install_locale, $settings_file, $database); drupal_set_title(st('Database configuration')); print theme('install_page', $output); exit; @@ -207,19 +198,11 @@ function install_change_settings($profil /** * Form API array definition for install_settings. */ -function install_settings_form(&$form_state, $profile, $install_locale, $settings_file, $db_url, $db_type, $db_prefix, $db_user, $db_pass, $db_host, $db_port, $db_path) { - if (empty($db_host)) { - $db_host = 'localhost'; - } - $db_types = drupal_detect_database_types(); - - // If both 'mysql' and 'mysqli' are available, we disable 'mysql': - if (isset($db_types['mysqli'])) { - unset($db_types['mysql']); - } +function install_settings_form(&$form_state, $profile, $install_locale, $settings_file, $database) { + $drivers = drupal_detect_database_types(); - if (count($db_types) == 0) { - $form['no_db_types'] = array( + if (!$drivers) { + $form['no_drivers'] = array( '#value' => st('Your web server does not appear to support any common database types. Check with your hosting provider to see if they offer any databases that Drupal supports.', array('@drupal-databases' => 'http://drupal.org/node/270#database')), ); } @@ -230,54 +213,51 @@ function install_settings_form(&$form_st '#description' => '

    '. st('To set up your @drupal database, enter the following information.', array('@drupal' => drupal_install_profile_name())) .'

    ', ); - if (count($db_types) > 1) { - $form['basic_options']['db_type'] = array( + if (count($drivers) == 1) { + $form['basic_options']['driver'] = array( + '#type' => 'hidden', + '#value' => current(array_keys($drivers)), + ); + $database_description = st('The name of the %driver database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('%driver' => current($drivers), '@drupal' => drupal_install_profile_name())); + } + else { + $form['basic_options']['driver'] = array( '#type' => 'radios', - '#title' => st('Database type'), + '#title' => st('Database driver'), '#required' => TRUE, - '#options' => $db_types, - '#default_value' => ($db_type ? $db_type : current($db_types)), + '#options' => $drivers, + '#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers), '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_name())), ); - $db_path_description = st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_name())); - } - else { - if (count($db_types) == 1) { - $db_types = array_values($db_types); - $form['basic_options']['db_type'] = array( - '#type' => 'hidden', - '#value' => $db_types[0], - ); - $db_path_description = st('The name of the %db_type database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('%db_type' => $db_types[0], '@drupal' => drupal_install_profile_name())); - } + $database_description = st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_name())); } // Database name - $form['basic_options']['db_path'] = array( + $form['basic_options']['database'] = array( '#type' => 'textfield', '#title' => st('Database name'), - '#default_value' => $db_path, + '#default_value' => empty($database['database']) ? '' : $database['database'], '#size' => 45, '#maxlength' => 45, '#required' => TRUE, - '#description' => $db_path_description + '#description' => $database_description, ); // Database username - $form['basic_options']['db_user'] = array( + $form['basic_options']['username'] = array( '#type' => 'textfield', '#title' => st('Database username'), - '#default_value' => $db_user, + '#default_value' => empty($database['username']) ? '' : $database['username'], '#size' => 45, '#maxlength' => 45, '#required' => TRUE, ); // Database username - $form['basic_options']['db_pass'] = array( + $form['basic_options']['password'] = array( '#type' => 'password', '#title' => st('Database password'), - '#default_value' => $db_pass, + '#default_value' => empty($database['password']) ? '' : $database['password'], '#size' => 45, '#maxlength' => 45, ); @@ -291,10 +271,10 @@ function install_settings_form(&$form_st ); // Database host - $form['advanced_options']['db_host'] = array( + $form['advanced_options']['host'] = array( '#type' => 'textfield', '#title' => st('Database host'), - '#default_value' => $db_host, + '#default_value' => empty($database['host']) ? 'localhost' : $database['host'], '#size' => 45, '#maxlength' => 45, '#required' => TRUE, @@ -302,10 +282,10 @@ function install_settings_form(&$form_st ); // Database port - $form['advanced_options']['db_port'] = array( + $form['advanced_options']['port'] = array( '#type' => 'textfield', '#title' => st('Database port'), - '#default_value' => $db_port, + '#default_value' => empty($database['port']) ? '' : $database['port'], '#size' => 45, '#maxlength' => 45, '#description' => st('If your database server is listening to a non-standard port, enter its number.'), @@ -329,7 +309,7 @@ function install_settings_form(&$form_st $form['errors'] = array(); $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file); - $form['_db_url'] = array('#type' => 'value'); + $form['_database'] = array('#type' => 'value'); $form['#action'] = "install.php?profile=$profile". ($install_locale ? "&locale=$install_locale" : ''); $form['#redirect'] = FALSE; } @@ -341,48 +321,43 @@ function install_settings_form(&$form_st */ function install_settings_form_validate($form, &$form_state) { global $db_url; - _install_settings_form_validate($form_state['values']['db_prefix'], $form_state['values']['db_type'], $form_state['values']['db_user'], $form_state['values']['db_pass'], $form_state['values']['db_host'], $form_state['values']['db_port'], $form_state['values']['db_path'], $form_state['values']['settings_file'], $form_state, $form); + _install_settings_form_validate($form_state['values'], $form_state['values']['settings_file'], $form_state, $form); } /** * Helper function for install_settings_validate. */ -function _install_settings_form_validate($db_prefix, $db_type, $db_user, $db_pass, $db_host, $db_port, $db_path, $settings_file, &$form_state, $form = NULL) { - global $db_url; - +function _install_settings_form_validate($database, $settings_file, &$form_state, $form = NULL) { + global $databases; // Verify the table prefix - if (!empty($db_prefix) && is_string($db_prefix) && !preg_match('/^[A-Za-z0-9_.]+$/', $db_prefix)) { + if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['dprefix'])) { form_set_error('db_prefix', st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $db_prefix)), 'error'); } - if (!empty($db_port) && !is_numeric($db_port)) { + if (!empty($database['port']) && !is_numeric($database['port'])) { form_set_error('db_port', st('Database port must be a number.')); } // Check database type - if (!isset($form)) { - $_db_url = is_array($db_url) ? $db_url['default'] : $db_url; - $db_type = substr($_db_url, 0, strpos($_db_url, '://')); - } - $databases = drupal_detect_database_types(); - if (!in_array($db_type, $databases)) { - form_set_error('db_type', st("In your %settings_file file you have configured @drupal to use a %db_type server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_name(), '%db_type' => $db_type))); + $database_types = drupal_detect_database_types(); + $driver = $database['driver']; + if (!isset($database_types[$driver])) { + form_set_error('driver', st("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_name(), '%driver' => $database['driver']))); } else { - // Verify - $db_url = $db_type .'://'. urlencode($db_user) . ($db_pass ? ':'. urlencode($db_pass) : '') .'@'. ($db_host ? urlencode($db_host) : 'localhost') . ($db_port ? ":$db_port" : '') .'/'. urlencode($db_path); if (isset($form)) { - form_set_value($form['_db_url'], $db_url, $form_state); + form_set_value($form['_database'], $database, $form_state); } - $success = array(); - - $function = 'drupal_test_'. $db_type; - if (!$function($db_url, $success)) { - if (isset($success['CONNECT'])) { - form_set_error('db_type', st('In order for Drupal to work, and to continue with the installation process, you must resolve all permission issues reported above. We were able to verify that we have permission for the following commands: %commands. For more help with configuring your database server, see the Installation and upgrading handbook. If you are unsure what any of this means you should probably contact your hosting provider.', array('%commands' => implode($success, ', ')))); + $class = "DatabaseInstaller_$driver"; + $test = new $class; + $databases = array('default' => array('default' => $database)); + $return = $test->test(); + if (!$return || $test->error) { + if (!empty($test->success)) { + form_set_error('db_type', st('In order for Drupal to work, and to continue with the installation process, you must resolve all permission issues reported above. We were able to verify that we have permission for the following commands: %commands. For more help with configuring your database server, see the Installation and upgrading handbook. If you are unsure what any of this means you should probably contact your hosting provider.', array('%commands' => implode($test->success, ', ')))); } else { - form_set_error('db_type', ''); + form_set_error('driver', ''); } } } @@ -394,9 +369,10 @@ function _install_settings_form_validate function install_settings_form_submit($form, &$form_state) { global $profile, $install_locale; + $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port'))); // Update global settings array and save - $settings['db_url'] = array( - 'value' => $form_state['values']['_db_url'], + $settings['databases'] = array( + 'value' => array('default' => array('default' => $database)), 'required' => TRUE, ); $settings['db_prefix'] = array( @@ -1150,3 +1126,4 @@ function install_configure_form_submit($ // Start the installer. install_main(); + === modified file 'modules/dblog/dblog.install' --- modules/dblog/dblog.install 2007-11-04 14:33:06 +0000 +++ modules/dblog/dblog.install 2008-02-27 05:43:42 +0000 @@ -65,7 +65,7 @@ function dblog_schema() { 'link' => array( 'type' => 'varchar', 'length' => 255, - 'not null' => TRUE, + 'not null' => FALSE, 'default' => '', 'description' => t('Link to view the result of the event.'), ), @@ -77,7 +77,7 @@ function dblog_schema() { 'referer' => array( 'type' => 'varchar', 'length' => 128, - 'not null' => TRUE, + 'not null' => FALSE, 'default' => '', 'description' => t('URL of referring page.'), ), @@ -102,3 +102,12 @@ function dblog_schema() { return $schema; } +/** + * Allow NULL values for links. + */ +function dblog_update_7001() { + $ret = array(); + db_change_field($ret, 'watchdog', 'link', 'link', array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'default' => '')); + db_change_field($ret, 'watchdog', 'referer', 'referer', array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'default' => '')); + return $ret; +} === modified file 'modules/filter/filter.module' --- modules/filter/filter.module 2008-02-20 13:46:36 +0000 +++ modules/filter/filter.module 2008-02-27 07:41:38 +0000 @@ -296,24 +296,18 @@ function filter_formats($index = NULL) { if (!isset($formats)) { $formats = array(); - $query = 'SELECT * FROM {filter_formats}'; + $query = db_select('filter_formats')->fields('*')->join('filter_formats', 'f')->orderBy('weight'); // Build query for selecting the format(s) based on the user's roles. - $args = array(); if (!$all) { - $where = array(); + $or = db_or()->condition('format', variable_get('filter_default_format', 1)); foreach ($user->roles as $rid => $role) { - $where[] = "roles LIKE '%%,%d,%%'"; - $args[] = $rid; + $or->condition('roles', 'LIKE', '%'. (int)$rid .'%'); } - $query .= ' WHERE '. implode(' OR ', $where) .' OR format = %d'; - $args[] = variable_get('filter_default_format', 1); + $query->condition($or); } - $result = db_query($query .' ORDER by weight', $args); - while ($format = db_fetch_object($result)) { - $formats[$format->format] = $format; - } + $formats = $query->execute()->fetchAllAssoc('format'); } if (isset($index)) { return isset($formats[$index]) ? $formats[$index] : FALSE; === modified file 'modules/menu/menu.module' --- modules/menu/menu.module 2008-02-20 13:46:36 +0000 +++ modules/menu/menu.module 2008-02-27 06:45:19 +0000 @@ -308,7 +308,7 @@ function menu_nodeapi(&$node, $op) { break; case 'delete': // Delete all menu module links that point to this node. - $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND module = 'menu'", $node->nid); + $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu'", array(':path' => 'node/'. $node->nid)); while ($m = db_fetch_array($result)) { menu_link_delete($m['mlid']); } @@ -320,10 +320,15 @@ function menu_nodeapi(&$node, $op) { $item = array(); if (isset($node->nid)) { // Give priority to the default menu - $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND menu_name = '%s' AND module = 'menu' ORDER BY mlid ASC", $node->nid, $menu_name, 0, 1)); + $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND menu_name = :menu_name AND module = 'menu' ORDER BY mlid ASC", array( + ':path' => 'node/'. $node->nid, + ':menu_name' => $menu_name, + ), 0, 1)); // Check all menus if a link does not exist in the default menu. if (!$mlid) { - $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'node/%d' AND module = 'menu' ORDER BY mlid ASC", $node->nid, 0, 1)); + $mlid = db_result(db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu' ORDER BY mlid ASC", array( + ':path' => 'node/'. $node->nid, + ), 0, 1)); } if ($mlid) { $item = menu_link_load($mlid); === modified file 'modules/node/node.module' --- modules/node/node.module 2008-02-20 13:46:36 +0000 +++ modules/node/node.module 2008-02-28 08:01:11 +0000 @@ -716,7 +716,7 @@ function node_load($param = array(), $re elseif (is_array($param)) { // Turn the conditions into a query. foreach ($param as $key => $value) { - $cond[] = 'n.'. db_escape_string($key) ." = '%s'"; + $cond[] = 'n.'. preg_replace('/[^A-Za-z0-9_]+/', '', $key) ." = '%s'"; $arguments[] = $value; } $cond = implode(' AND ', $cond); @@ -1487,6 +1487,7 @@ function node_menu() { 'title' => $type->name, 'page callback' => 'drupal_get_form', 'page arguments' => array('node_type_form', $type), + 'access arguments' => array('administer content types'), 'file' => 'content_types.inc', 'type' => MENU_CALLBACK, ); @@ -2203,11 +2204,11 @@ function node_access_acquire_grants($nod */ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) { if ($delete) { - $query = 'DELETE FROM {node_access} WHERE nid = %d'; + $query = db_delete('node_access_write_grants_delete', 'node_access')->condition('nid', $node->nid); if ($realm) { - $query .= " AND realm in ('%s', 'all')"; + $query->condition('realm', 'IN', array($realm, 'all')); } - db_query($query, $node->nid, $realm); + $query->execute(); } // Only perform work when node_access modules are active. === modified file 'modules/path/path.admin.inc' --- modules/path/path.admin.inc 2008-01-08 10:35:40 +0000 +++ modules/path/path.admin.inc 2008-03-01 02:00:22 +0000 @@ -19,12 +19,14 @@ function path_admin_overview($keys = NUL $multilanguage = (module_exists('locale') || $count); if ($keys) { - // Replace wildcards with MySQL/PostgreSQL wildcards. + // Replace wildcards with PDO wildcards. $keys = preg_replace('!\*+!', '%', $keys); - $sql = "SELECT * FROM {url_alias} WHERE dst LIKE '%%%s%%'"; + $sql = "SELECT * FROM {url_alias} WHERE dst LIKE :keys"; + $args = array(':keys' => '%'. $keys .'%'); } else { $sql = 'SELECT * FROM {url_alias}'; + $args = array(); } $header = array( array('data' => t('Alias'), 'field' => 'dst', 'sort' => 'asc'), @@ -36,7 +38,7 @@ function path_admin_overview($keys = NUL $header[2] = array('data' => t('Language'), 'field' => 'language'); } $sql .= tablesort_sql($header); - $result = pager_query($sql, 50, 0 , NULL, $keys); + $result = pager_query($sql, 50, 0 , NULL, $args); $rows = array(); $destination = drupal_get_destination(); === modified file 'modules/profile/profile.admin.inc' --- modules/profile/profile.admin.inc 2008-02-18 16:53:36 +0000 +++ modules/profile/profile.admin.inc 2008-02-28 07:22:01 +0000 @@ -396,7 +396,7 @@ function profile_field_delete_submit($fo */ function profile_admin_settings_autocomplete($string) { $matches = array(); - $result = db_query_range("SELECT category FROM {profile_fields} WHERE LOWER(category) LIKE LOWER('%s%%')", $string, 0, 10); + $result = db_query_range("SELECT category FROM {profile_fields} WHERE LOWER(category) LIKE LOWER(:category)", array(':category' => $string .'%'), 0, 10); while ($data = db_fetch_object($result)) { $matches[$data->category] = check_plain($data->category); } === modified file 'modules/profile/profile.pages.inc' --- modules/profile/profile.pages.inc 2007-12-08 14:06:20 +0000 +++ modules/profile/profile.pages.inc 2008-02-28 07:22:01 +0000 @@ -110,7 +110,10 @@ function profile_browse() { function profile_autocomplete($field, $string) { $matches = array(); if (db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE fid = %d AND autocomplete = 1", $field))) { - $result = db_query_range("SELECT value FROM {profile_values} WHERE fid = %d AND LOWER(value) LIKE LOWER('%s%%') GROUP BY value ORDER BY value ASC", $field, $string, 0, 10); + $result = db_query_range("SELECT value FROM {profile_values} WHERE fid = :fid AND LOWER(value) LIKE LOWER(:value) GROUP BY value ORDER BY value ASC", array( + ':fid' => $field, + ':value' => $string .'%', + ), 0, 10); while ($data = db_fetch_object($result)) { $matches[$data->value] = check_plain($data->value); } === modified file 'modules/search/search.module' --- modules/search/search.module 2008-02-20 13:46:36 +0000 +++ modules/search/search.module 2008-02-29 18:21:32 +0000 @@ -288,10 +288,14 @@ function search_update_totals() { $total = db_result(db_query("SELECT SUM(score) FROM {search_index} WHERE word = '%s'", $word)); // Apply Zipf's law to equalize the probability distribution $total = log10(1 + 1/(max(1, $total))); - db_query("UPDATE {search_total} SET count = %f WHERE word = '%s'", $total, $word); - if (!db_affected_rows()) { - db_query("INSERT INTO {search_total} (word, count) VALUES ('%s', %f)", $word, $total); - } + $insert_fields = array( + 'word' => $word, + 'count' => $total, + ); + $update_fields = array( + 'count' => $total, + ); + db_insert('search_update_totals', 'search_total')->fields($insert_fields)->duplicateUpdate($update_fields)->execute(); } // Find words that were deleted from search_index, but are still in // search_total. We use a LEFT JOIN between the two tables and keep only the @@ -786,7 +790,7 @@ function search_parse_query($text) { $any |= $num_new_scores; if ($q) { $queryor[] = $q; - $arguments[] = $or; + $arguments[] = "% $or %"; } } if (count($queryor)) { @@ -801,7 +805,7 @@ function search_parse_query($text) { list($q, $num_new_scores, $num_valid_words) = _search_parse_query($key, $arguments2); if ($q) { $query[] = $q; - $arguments[] = $key; + $arguments[] = "% $key %"; if (!$num_valid_words) { $simple = FALSE; } @@ -818,7 +822,7 @@ function search_parse_query($text) { list($q) = _search_parse_query($key, $arguments2, TRUE); if ($q) { $query[] = $q; - $arguments[] = $key; + $arguments[] = "% $key %"; $simple = FALSE; } } @@ -852,7 +856,7 @@ function _search_parse_query(&$word, &$s } } // Return matching snippet and number of added words - return array("d.data ". ($not ? 'NOT ' : '') ."LIKE '%% %s %%'", $num_new_scores, $num_valid_words); + return array("d.data ". ($not ? 'NOT ' : '') ."LIKE '%s'", $num_new_scores, $num_valid_words); } /** === modified file 'modules/statistics/statistics.admin.inc' --- modules/statistics/statistics.admin.inc 2008-01-08 10:35:40 +0000 +++ modules/statistics/statistics.admin.inc 2008-02-28 07:22:01 +0000 @@ -107,8 +107,8 @@ function statistics_top_visitors() { * Menu callback; presents the "referrer" page. */ function statistics_top_referrers() { - $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE '%%%s%%' AND url <> '' GROUP BY url"; - $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE '%%%s%%'"; + $query = "SELECT url, COUNT(url) AS hits, MAX(timestamp) AS last FROM {accesslog} WHERE url NOT LIKE :host AND url <> '' GROUP BY url"; + $query_cnt = "SELECT COUNT(DISTINCT(url)) FROM {accesslog} WHERE url <> '' AND url NOT LIKE :host"; drupal_set_title(t('Top referrers in the past %interval', array('%interval' => format_interval(variable_get('statistics_flush_accesslog_timer', 259200))))); $header = array( @@ -118,7 +118,7 @@ function statistics_top_referrers() { ); $query .= tablesort_sql($header); - $result = pager_query($query, 30, 0, $query_cnt, $_SERVER['HTTP_HOST']); + $result = pager_query($query, 30, 0, $query_cnt, array(':host' => '%'. $_SERVER['HTTP_HOST'] .'%')); $rows = array(); while ($referrer = db_fetch_object($result)) { === modified file 'modules/statistics/statistics.module' --- modules/statistics/statistics.module 2008-02-20 13:46:36 +0000 +++ modules/statistics/statistics.module 2008-02-27 07:41:38 +0000 @@ -51,17 +51,32 @@ function statistics_exit() { // We are counting content views. if ((arg(0) == 'node') && is_numeric(arg(1)) && arg(2) == '') { // A node has been viewed, so update the node's counters. - db_query('UPDATE {node_counter} SET daycount = daycount + 1, totalcount = totalcount + 1, timestamp = %d WHERE nid = %d', time(), arg(1)); - // If we affected 0 rows, this is the first time viewing the node. - if (!db_affected_rows()) { - // We must create a new row to store counters for the new node. - db_query('INSERT INTO {node_counter} (nid, daycount, totalcount, timestamp) VALUES (%d, 1, 1, %d)', arg(1), time()); - } + $fields = array( + 'daycount' => 1, + 'totalcount' => 1, + 'nid' => arg(1), + 'timestamp' => time(), + ); + $update = array( + 'daycount = daycount + 1', + 'totalcount = totalcount + 1', + 'timestamp' => time(), + ); + db_insert('statistics_exit_node_counter', 'node_counter')->fields($fields)->duplicateUpdate($update)->execute(); } } if ((variable_get('statistics_enable_access_log', 0)) && (module_invoke('throttle', 'status') == 0)) { // Log this page access. - db_query("INSERT INTO {accesslog} (title, path, url, hostname, uid, sid, timer, timestamp) values('%s', '%s', '%s', '%s', %d, '%s', %d, %d)", strip_tags(drupal_get_title()), $_GET['q'], referer_uri(), ip_address(), $user->uid, session_id(), timer_read('page'), time()); + db_insert('statistics_exit_accesslog', 'accesslog')->fields(array( + 'title' => strip_tags(drupal_get_title()), + 'path' => $_GET['q'], + 'url' => referer_uri(), + 'hostname' => ip_address(), + 'uid' => $user->uid, + 'sid' => session_id(), + 'timer' => timer_read('page'), + 'timestamp' => time(), + ))->execute(); } } === modified file 'modules/taxonomy/taxonomy.pages.inc' --- modules/taxonomy/taxonomy.pages.inc 2008-02-06 19:38:26 +0000 +++ modules/taxonomy/taxonomy.pages.inc 2008-02-28 07:22:01 +0000 @@ -119,7 +119,10 @@ function taxonomy_autocomplete($vid, $st $last_string = trim(array_pop($array)); $matches = array(); if ($last_string != '') { - $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = %d AND LOWER(t.name) LIKE LOWER('%%%s%%')", 't', 'tid'), $vid, $last_string, 0, 10); + $result = db_query_range(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE t.vid = :vid AND LOWER(t.name) LIKE LOWER(:last_string)", 't', 'tid'), array( + ':vid' => $vid, + ':last_string' => '%'. $last_string .'%', + ), 0, 10); $prefix = count($array) ? implode(', ', $array) .', ' : ''; === modified file 'modules/trigger/trigger.module' --- modules/trigger/trigger.module 2008-02-17 22:15:04 +0000 +++ modules/trigger/trigger.module 2008-02-28 08:09:33 +0000 @@ -299,7 +299,8 @@ function trigger_comment($a1, $op) { actions_do($aid, $objects[$action_info['type']], $context); } else { - actions_do($aid, (object) $a1, $context); + $a1 = (object) $a1; + actions_do($aid, $a1, $context); } } } @@ -391,8 +392,9 @@ function trigger_taxonomy($op, $type, $a 'hook' => 'taxonomy', 'op' => $op ); + $_array = (object) $array; foreach ($aids as $aid => $action_info) { - actions_do($aid, (object) $array, $context); + actions_do($aid, $_array, $context); } } === modified file 'modules/user/user.module' --- modules/user/user.module 2008-02-20 13:46:36 +0000 +++ modules/user/user.module 2008-02-27 06:53:18 +0000 @@ -1163,10 +1163,15 @@ function user_set_authmaps($account, $au foreach ($authmaps as $key => $value) { $module = explode('_', $key, 2); if ($value) { - db_query("UPDATE {authmap} SET authname = '%s' WHERE uid = %d AND module = '%s'", $value, $account->uid, $module[1]); - if (!db_affected_rows()) { - db_query("INSERT INTO {authmap} (authname, uid, module) VALUES ('%s', %d, '%s')", $value, $account->uid, $module[1]); - } + $insert_fields = array( + 'authname' => $value, + 'uid' => $account->uid, + 'module' => $module[1], + ); + $update_fields = array( + 'authname' => $value, + ); + db_insert('user_set_authmaps', 'authmap')->fields($insert_fields)->duplicateUpdate($update_fields)->execute(); } else { db_query("DELETE FROM {authmap} WHERE uid = %d AND module = '%s'", $account->uid, $module[1]); === modified file 'modules/user/user.pages.inc' --- modules/user/user.pages.inc 2008-02-18 16:53:36 +0000 +++ modules/user/user.pages.inc 2008-02-28 07:22:01 +0000 @@ -12,7 +12,7 @@ function user_autocomplete($string = '') { $matches = array(); if ($string) { - $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10); + $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER(:name)", array(':name' => $string .'%'), 0, 10); while ($user = db_fetch_object($result)) { $matches[$user->name] = check_plain($user->name); } === modified file 'sites/default/default.settings.php' (properties changed) --- sites/default/default.settings.php 2007-12-20 09:35:09 +0000 +++ sites/default/default.settings.php 2008-02-27 06:45:19 +0000 @@ -46,20 +46,76 @@ /** * Database settings: * - * Note that the $db_url variable gets parsed using PHP's built-in - * URL parser (i.e. using the "parse_url()" function) so make sure - * not to confuse the parser. If your username, password - * or database name contain characters used to delineate - * $db_url parts, you can escape them via URI hex encodings: - * - * : = %3a / = %2f @ = %40 - * + = %2b ( = %28 ) = %29 - * ? = %3f = = %3d & = %26 - * - * To specify multiple connections to be used in your site (i.e. for - * complex custom modules) you can also specify an associative array - * of $db_url variables with the 'default' element used until otherwise - * requested. + * The $databases array specifies the database connection or + * connections that Drupal may use. Drupal is able to connect + * to multiple databases, including multiple types of databases, + * during the same request. + * + * Each database connection is specified as an array of settings, + * similar to the following: + * + * array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * + * The "driver" property indicates what Drupal database driver the + * connection should use. This is usually the same as the name of the + * database type, such as mysql or sqlite, but not always. The other + * properties will vary depending on the driver. For SQLite, you must + * specify a database. For most other drivers, you must specify a username, + * password, host, and database name. + * + * Some database engines support transactions. In order to enable + * transaction support for a given database, set the 'transaction' key + * to TRUE. To disable it, set it to FALSE. Note that the default value + * varies by driver. For MySQL, the default is FALSE since MyISAM tables + * do not support transactions. + * + * For each database, you may optionally specify multiple "target" databases. + * A target database allows Drupal to try to send certain queries to a + * different database if it can but fall back to the default connection if not. + * That is useful for master/slave replication, as Drupal may try to connect + * to a slave server when appropriate and if one is not available will simply + * fall back to the single master server. + * + * The general format for the $databases array is as follows: + * + * $databases['default']['default'] = $info_array; + * $databases['default']['slave'][] = $info_array; + * $databases['default']['slave'][] = $info_array; + * $databases['extra'] = $info_array; + * + * In the above example, $info_array is an array of settings described above. + * The first line sets a "default" database that has one master database + * (the second level default). The second and third lines create an array + * of potential slave databases. Drupal will select one at random for a given + * request as needed. The fourth line creates a new database with a name of + * "extra". Since no target is specified, it is assumed to be "default", that + * is, only one server. + * + * For a single database configuration, the following is sufficient: + * + * $databases = array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * + * That is equivalent to: + * + * $databases['default']['default'] = array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); * * You can optionally set prefixes for some or all database table names * by using the $db_prefix setting. If a prefix is specified, the table @@ -85,12 +141,27 @@ * 'sequences' => 'shared_', * ); * - * Database URL format: - * $db_url = 'mysql://username:password@localhost/databasename'; - * $db_url = 'mysqli://username:password@localhost/databasename'; - * $db_url = 'pgsql://username:password@localhost/databasename'; + * Database configuration format: + * $databases = array( + * 'driver' => 'mysql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * $databases = array( + * 'driver' => 'pgsql', + * 'database' => 'databasename', + * 'username' => 'username', + * 'password' => 'password', + * 'host' => 'localhost', + * ); + * $databases = array( + * 'driver' => 'sqlite', + * 'database' => 'databasefilename', + * ); */ -$db_url = 'mysql://username:password@localhost/databasename'; +$databases = array(); $db_prefix = ''; /**