Index: includes/tablesort.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/tablesort.inc,v retrieving revision 1.48 diff -u -p -r1.48 tablesort.inc --- includes/tablesort.inc 14 Apr 2008 17:48:33 -0000 1.48 +++ includes/tablesort.inc 24 Sep 2008 06:24:11 -0000 @@ -40,7 +40,7 @@ function tablesort_sql($header, $before $ts = tablesort_init($header); if ($ts['sql']) { // Based on code from db_escape_table(), but this can also contain a dot. - $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']); + $field = preg_replace('/[^A-Za-z0-9_.\[\]]+/', '', db_escape_constraint($ts['sql'])); // Sort order can only be ASC or DESC. $sort = drupal_strtoupper($ts['sort']); Index: includes/database/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/database.inc,v retrieving revision 1.6 diff -u -p -r1.6 database.inc --- includes/database/database.inc 20 Sep 2008 20:22:24 -0000 1.6 +++ includes/database/database.inc 24 Sep 2008 06:24:12 -0000 @@ -146,6 +146,59 @@ abstract class DatabaseConnection extend */ public $lastStatement; + /** + * Drupal provides the following functions for portably generating SQL + * functions as strings to be merged into your SQL statements. + * + * Most implementation reference from ADOdb + * + * @link http://phplens.com/lens/adodb/tips_portable_sql.htm + */ + + /** + * String to use to quote identifiers and names. + */ + protected $nameQuote = '"'; + + /** + * Default concat operator. Change to || for Oracle/Interbase. + */ + protected $concat_operator = '+'; + + /** + * Uppercase function. + */ + public $upperCase = 'UPPER'; + + /** + * Random function. + */ + public $random = 'RAND()'; + + /** + * String length operator. + */ + public $length = 'LENGTH'; + + /** + * Substring operator. + */ + public $substr = 'SUBSTR'; + + /** + * Portably concatenate strings. + */ + public function concat($args) { + return implode($this->concat_operator, $args); + } + + /** + * Returns a string that is the equivalent of MySQL IFNULL or Oracle NVL. + */ + public function ifNull($expr1, $expr2) { + return " CASE WHEN $expr1 IS NULL THEN $expr2 ELSE $expr1 END "; + } + 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); @@ -249,6 +302,23 @@ abstract class DatabaseConnection extend } /** + * Replace all quote characters in a query. + * + * Queries sent to Drupal should wrap all identifiers and names in square + * brackets. This function will search for this syntax and replace it as + * corresponding escape characters, based on the database engine specific + * requirement. + * + * @param $sql + * A string containing a partial or entire SQL query. + * @return + * The properly-escaped string. + */ + protected function replaceQuotes($sql) { + return strtr($sql, array('[' => $this->nameQuote, ']' => $this->nameQuote)); + } + + /** * Prepare a query string and return the prepared statement. * * This method statically caches prepared statements, reusing them when @@ -263,6 +333,7 @@ abstract class DatabaseConnection extend protected function prepareQuery($query) { static $statements = array(); $query = self::prefixTables($query); + $query = self::replaceQuotes($query); if (empty($statements[$query])) { $statements[$query] = parent::prepare($query); } @@ -352,6 +423,11 @@ abstract class DatabaseConnection extend } } catch (PDOException $e) { + // TODO: remove debug + openlog("siren", LOG_PID | LOG_PERROR, LOG_LOCAL0); + syslog(LOG_ERR, "PDOException: " . $query); + closelog(); + if (!function_exists('module_implements')) { _db_need_install(); } @@ -506,6 +582,30 @@ abstract class DatabaseConnection extend } /** + * Escapes a constraint name string. + * + * Force all constraint names as reserved word safe with quote characters. + * The generic quote characters will further more replace as + * database-specific quote characters. + * + * @return + * The sanitized constraint name string. + */ + public function escapeConstraint($field) { + // Check if already come with [], or else escape it. + if (!strstr($field, '[') && !strstr($field, ']')) { + if (strstr($field, '.')) { + $field = preg_replace('/(.*)\.([A-Za-z0-9_]+)/', '\1.[\2]', $field); + } + else { + $field = preg_replace('/([A-Za-z0-9_]+)/', '[\1]', $field); + } + } + + return $field; + } + + /** * Returns a new DatabaseTransaction object on this connection. * * @param $required @@ -1123,6 +1223,58 @@ class DatabaseStatement extends PDOState */ /** + * Return the name of the SQL strtoupper function. + */ +function db_strtoupper() { + return Database::getActiveConnection()->upperCase; +} + +/** + * Return the SQL to generate a random number between 0.00 and 1.00. + */ +function db_rand() { + return Database::getActiveConnection()->random; +} + +/** + * Return the name of the SQL strlen function. + */ +function db_strlen() { + return Database::getActiveConnection()->length; +} + +/** + * Return the name of the SQL substr function. + */ +function db_substr() { + return Database::getActiveConnection()->substr; +} + +/** + * Different SQL databases used different methods to combine strings together. + * This function provides a wrapper. + * + * @param ... + * Variable number of string parameters. + * @return + * Portably concatenate strings. + */ +function db_strcat() { + $args = func_get_args(); + return Database::getActiveConnection()->concat($args); +} + +/** + * Returns a string that is the equivalent of MySQL IFNULL or Oracle NVL. + * + * @return + * If $expr1 is not NULL, returns $expr1; otherwise it returns $expr2. + */ +function db_if_null($expr1, $expr2) { + return Database::getActiveConnection()->ifNull($expr1, $expr2); +} + +/** * Execute an arbitrary query string against the active database. * * Do not use this function for INSERT, UPDATE, or DELETE queries. Those should @@ -1345,6 +1497,18 @@ function db_escape_table($table) { } /** + * Restrict a dynamic table, column or constraint name as reserved word safe. + * + * @param $field + * The field name to escape. + * @return + * The escaped field name as a string. + */ +function db_escape_constraint($field) { + return Database::getActiveConnection()->escapeConstraint($field); +} + +/** * Perform an SQL query and return success or failure. * * @param $sql @@ -1480,7 +1644,7 @@ function db_type_placeholder($type) { case 'char': case 'text': case 'datetime': - return '\'%s\''; + return '%s'; case 'numeric': // Numeric values are arbitrary precision numbers. Syntacically, numerics @@ -1830,6 +1994,22 @@ function _db_query_process_args($query, $options['target'] = 'default'; } + // TODO: remove debug + openlog("siren", LOG_PID | LOG_PERROR, LOG_LOCAL0); + if (preg_match('/[^\[\'{a-z0-9_:]([a-z0-9_{}]{3,})[^\]\'}a-z0-9_]/Ds', $query, $matches) && !preg_match('/[0-9]/', $matches[1])) { + syslog(LOG_ERR, "No []: " . $query); + } + if (preg_match("/'%s'/", $query)) { + syslog(LOG_ERR, "OLD '%s': " . $query); + } + if (preg_match("/!=/", $query)) { + syslog(LOG_ERR, "OLD !=: " . $query); + } + if (preg_match("/[ ,]''[ ,]/", $query)) { + syslog(LOG_ERR, "EMPTY STRING '': " . $query); + } + closelog(); + // Temporary backward-compatibliity hacks. Remove later. $old_query = $query; $query = str_replace(array('%n', '%d', '%f', '%b', "'%s'", '%s'), '?', $old_query); Index: includes/database/query.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/query.inc,v retrieving revision 1.3 diff -u -p -r1.3 query.inc --- includes/database/query.inc 15 Sep 2008 05:00:48 -0000 1.3 +++ includes/database/query.inc 24 Sep 2008 06:24:12 -0000 @@ -416,7 +416,7 @@ class InsertQuery extends Query { $placeholders = array_pad($placeholders, count($this->defaultFields), 'default'); $placeholders = array_pad($placeholders, count($this->insertFields), '?'); - return 'INSERT INTO {'. $this->table .'} ('. implode(', ', $insert_fields) .') VALUES ('. implode(', ', $placeholders) .')'; + return 'INSERT INTO [{' . $this->table . '}] ([' . implode('], [', $insert_fields) . ']) VALUES (' . implode(', ', $placeholders) . ')'; } } @@ -751,7 +751,7 @@ class DeleteQuery extends Query implemen } public function __toString() { - $query = 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; + $query = 'DELETE FROM [{' . $this->connection->escapeTable($this->table) . '}] '; if (count($this->condition)) { $this->condition->compile($this->connection); @@ -919,16 +919,16 @@ class UpdateQuery extends Query implemen $fields = $this->fields; $update_fields = array(); foreach ($this->expressionFields as $field => $data) { - $update_fields[] = $field . '=' . $data['expression']; + $update_fields[] = '[' . $field . '] = ' . $data['expression']; unset($fields[$field]); } $max_placeholder = 0; foreach ($fields as $field => $value) { - $update_fields[] = $field . '=:db_update_placeholder_' . ($max_placeholder++); + $update_fields[] = '[' . $field . '] = :db_update_placeholder_' . ($max_placeholder++); } - $query = 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields); + $query = 'UPDATE [{' . $this->connection->escapeTable($this->table) . '}] SET ' . implode(', ', $update_fields); if (count($this->condition)) { $this->condition->compile($this->connection); @@ -1060,7 +1060,7 @@ class DatabaseCondition implements Query $arguments[$placeholder] = $value; $placeholders[] = $placeholder; } - $condition_fragments[] = ' (' . $condition['field'] . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; + $condition_fragments[] = ' (' . db_escape_constraint($condition['field']) . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; } } Index: includes/database/select.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/select.inc,v retrieving revision 1.4 diff -u -p -r1.4 select.inc --- includes/database/select.inc 15 Sep 2008 05:00:48 -0000 1.4 +++ includes/database/select.inc 24 Sep 2008 06:24:12 -0000 @@ -628,10 +628,10 @@ class SelectQuery extends Query implemen // FIELDS and EXPRESSIONS $fields = array(); foreach ($this->fields as $alias => $field) { - $fields[] = (isset($field['table']) ? $field['table'] . '.' : '') . $field['field'] . ' AS ' . $field['alias']; + $fields[] = (isset($field['table']) ? '[' . $field['table'] . '].' : '') . '[' . $field['field'] . '] AS [' . $field['alias'] . ']'; } foreach ($this->expressions as $alias => $expression) { - $fields[] = $expression['expression'] . ' AS ' . $expression['alias']; + $fields[] = $expression['expression'] . ' AS [' . $expression['alias'] . ']'; } $query .= implode(', ', $fields); @@ -642,7 +642,7 @@ class SelectQuery extends Query implemen if (isset($table['join type'])) { $query .= $table['join type'] . ' JOIN '; } - $query .= '{' . $this->connection->escapeTable($table['table']) . '} AS ' . $table['alias']; + $query .= '[{' . $this->connection->escapeTable($table['table']) . '}] AS [' . $table['alias'] . ']'; if (!empty($table['condition'])) { $query .= ' ON ' . $table['condition']; } Index: includes/database/mysql/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/mysql/database.inc,v retrieving revision 1.3 diff -u -p -r1.3 database.inc --- includes/database/mysql/database.inc 21 Sep 2008 15:27:20 -0000 1.3 +++ includes/database/mysql/database.inc 24 Sep 2008 06:24:12 -0000 @@ -14,6 +14,16 @@ class DatabaseConnection_mysql extends DatabaseConnection { protected $transactionSupport; + protected $nameQuote = '`'; + + public function concat($args) { + $return = implode(', ', $args); + return (strlen($return) > 0) ? "CONCAT($return)" : ''; + } + + public function ifNull($expr1, $expr2) { + return " IFNULL($expr1, $expr2) "; + } public function __construct(Array $connection_options = array()) { @@ -36,12 +46,12 @@ class DatabaseConnection_mysql extends D // Enable MySQL's "strict mode", which removes most of MySQL's // "just be lazy" behaviors that end up causing more trouble than they're worth. - $this->exec('SET sql_mode=STRICT_ALL_TABLES'); + $this->exec('SET SQL_MODE = STRICT_ALL_TABLES'); } public function queryRange($query, Array $args, $from, $count, Array $options) { // Backward compatibility hack, temporary. - $query = str_replace(array('%d' , '%f' , '%b' , "'%s'"), '?', $query); + $query = str_replace(array('%n', '%d', '%f', '%b', "'%s'", '%s'), '?', $query); return $this->query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options); } @@ -83,7 +93,6 @@ class DatabaseConnection_mysql extends D } } - /** * @} End of "ingroup database". */ Index: includes/database/mysql/query.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/mysql/query.inc,v retrieving revision 1.2 diff -u -p -r1.2 query.inc --- includes/database/mysql/query.inc 15 Sep 2008 20:48:07 -0000 1.2 +++ includes/database/mysql/query.inc 24 Sep 2008 06:24:12 -0000 @@ -41,7 +41,7 @@ class InsertQuery_mysql extends InsertQu // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); - $query = "INSERT $delay INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; + $query = "INSERT $delay INTO [{" . $this->table . '}] ([' . implode('], [', $insert_fields) . ']) VALUES '; $max_placeholder = 0; $values = array(); @@ -137,7 +137,7 @@ class MergeQuery_mysql extends MergeQuer $insert_fields = $this->insertFields + $this->keyFields; - $query = "INSERT INTO {" . $this->table . '} (' . implode(', ', array_keys($insert_fields)) . ') VALUES '; + $query = "INSERT INTO [{" . $this->table . '}] ([' . implode('], [', array_keys($insert_fields)) . ']) VALUES '; $max_placeholder = 0; $values = array(); @@ -153,12 +153,12 @@ class MergeQuery_mysql extends MergeQuer $max_placeholder = 0; $update = array(); foreach ($this->expressionFields as $field => $data) { - $update[] = $field . '=' . $data['expression']; + $update[] = '[' . $field . '] = ' . $data['expression']; unset($update_fields[$field]); } foreach ($update_fields as $field => $value) { - $update[] = ($field . '=:db_update_placeholder_' . $max_placeholder++); + $update[] = ('[' . $field . '] = :db_update_placeholder_' . $max_placeholder++); } $query .= implode(', ', $update); Index: includes/database/mysql/schema.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/mysql/schema.inc,v retrieving revision 1.3 diff -u -p -r1.3 schema.inc --- includes/database/mysql/schema.inc 19 Sep 2008 20:30:32 -0000 1.3 +++ includes/database/mysql/schema.inc 24 Sep 2008 06:24:12 -0000 @@ -6,7 +6,6 @@ * Database schema code for MySQL database servers. */ - /** * @ingroup schemaapi * @{ @@ -22,7 +21,6 @@ class DatabaseSchema_mysql extends Datab return (bool) $this->connection->query("SHOW COLUMNS FROM {" . $this->connection->escapeTable($table) . "} LIKE '" . $this->connection->escapeTable($column) . "'", array(), array())->fetchField(); } - /** * Generate SQL to create a new table from a Drupal schema definition. * @@ -38,7 +36,7 @@ class DatabaseSchema_mysql extends Datab $table['mysql_suffix'] = "/*!40100 DEFAULT CHARACTER SET UTF8 */"; } - $sql = "CREATE TABLE {" . $name . "} (\n"; + $sql = "CREATE TABLE [{" . $name . "}] (\n"; // Add the SQL statement for each field. foreach ($table['fields'] as $field_name => $field) { @@ -71,7 +69,11 @@ class DatabaseSchema_mysql extends Datab * The field specification, as per the schema data structure format. */ protected function createFieldSql($name, $spec) { - $sql = "`" . $name . "` " . $spec['mysql_type']; + $sql = "[" . $name . "] " . $spec['mysql_type']; + + if (($spec['type'] == 'text') || ($spec['type'] == 'blob')) { + unset($spec['default']); + } if (isset($spec['length'])) { $sql .= '(' . $spec['length'] . ')'; @@ -81,7 +83,7 @@ class DatabaseSchema_mysql extends Datab } if (!empty($spec['unsigned'])) { - $sql .= ' unsigned'; + $sql .= ' UNSIGNED'; } if (!empty($spec['not null'])) { @@ -89,7 +91,7 @@ class DatabaseSchema_mysql extends Datab } if (!empty($spec['auto_increment'])) { - $sql .= ' auto_increment'; + $sql .= ' AUTO_INCREMENT'; } if (isset($spec['default'])) { @@ -173,23 +175,20 @@ class DatabaseSchema_mysql extends Datab return $map; } - - - protected function createKeysSql($spec) { $keys = array(); if (!empty($spec['primary key'])) { - $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')'; + $keys[] = 'PRIMARY KEY (' . $this->createKeySql($spec['primary key']) . ')'; } if (!empty($spec['unique keys'])) { foreach ($spec['unique keys'] as $key => $fields) { - $keys[] = 'UNIQUE KEY ' . $key .' ('. $this->createKeysSqlHelper($fields) . ')'; + $keys[] = 'UNIQUE KEY [' . $key . '] (' . $this->createKeySql($fields) . ')'; } } if (!empty($spec['indexes'])) { foreach ($spec['indexes'] as $index => $fields) { - $keys[] = 'INDEX ' . $index . ' (' . $this->createKeysSqlHelper($fields) . ')'; + $keys[] = 'INDEX [' . $index . '] (' . $this->createKeySql($fields) . ')'; } } @@ -200,34 +199,21 @@ class DatabaseSchema_mysql extends Datab $ret = array(); foreach ($fields as $field) { if (is_array($field)) { - $ret[] = $field[0] . '(' . $field[1] . ')'; - } - else { - $ret[] = $field; - } - } - return implode(', ', $ret); - } - - protected function createKeysSqlHelper($fields) { - $ret = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $ret[] = $field[0] . '(' . $field[1] . ')'; + $ret[] = '[' . $field[0] . '] (' . $field[1] . ')'; } else { - $ret[] = $field; + $ret[] = '[' . $field . ']'; } } return implode(', ', $ret); } public function renameTable(&$ret, $table, $new_name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] RENAME TO [{' . $new_name . '}]'); } public function dropTable(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {' . $table . '}'); + $ret[] = update_sql('DROP TABLE [{' . $table . '}]'); } public function addField(&$ret, $table, $field, $spec, $keys_new = array()) { @@ -236,7 +222,7 @@ class DatabaseSchema_mysql extends Datab $fixnull = TRUE; $spec['not null'] = FALSE; } - $query = 'ALTER TABLE {' . $table . '} ADD '; + $query = 'ALTER TABLE [{' . $table . '}] ADD '; $query .= $this->createFieldSql($field, $this->processField($spec)); if (count($keys_new)) { $query .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new)); @@ -244,7 +230,7 @@ class DatabaseSchema_mysql extends Datab $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']); + $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'] . ')')); } @@ -255,7 +241,7 @@ class DatabaseSchema_mysql extends Datab } public function dropField(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP ' . $field); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP [' . $field . ']'); } public function fieldSetDefault(&$ret, $table, $field, $default) { @@ -266,40 +252,40 @@ class DatabaseSchema_mysql extends Datab $default = is_string($default) ? "'$default'" : $default; } - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN ' . $field . ' SET DEFAULT ' . $default); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ALTER COLUMN [' . $field . '] SET DEFAULT ' . $default); } public function fieldSetNoDefault(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN ' . $field . ' DROP DEFAULT'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ALTER COLUMN [' . $field . '] DROP DEFAULT'); } public function addPrimaryKey(&$ret, $table, $fields) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')'); } public function dropPrimaryKey(&$ret, $table) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP PRIMARY KEY'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP PRIMARY KEY'); } public function addUniqueKey(&$ret, $table, $name, $fields) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD UNIQUE KEY ' . $name . ' (' . $this->createKeySql($fields) . ')'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ADD UNIQUE KEY [' . $name . '] (' . $this->createKeySql($fields) . ')'); } public function dropUniqueKey(&$ret, $table, $name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP KEY ' . $name); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP KEY [' . $name . ']'); } public function addIndex(&$ret, $table, $name, $fields) { - $query = 'ALTER TABLE {' . $table . '} ADD INDEX ' . $name . ' (' . $this->createKeySql($fields) . ')'; + $query = 'ALTER TABLE [{' . $table . '}] ADD INDEX [' . $name . '] (' . $this->createKeySql($fields) . ')'; $ret[] = update_sql($query); } public function dropIndex(&$ret, $table, $name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP INDEX ' . $name); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP INDEX [' . $name . ']'); } public function changeField(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) { - $sql = 'ALTER TABLE {' . $table . '} CHANGE ' . $field . ' ' . $this->createFieldSql($field_new, $this->processField($spec)); + $sql = 'ALTER TABLE [{' . $table . '}] CHANGE [' . $field . '] ' . $this->createFieldSql($field_new, $this->processField($spec)); if (count($keys_new)) { $sql .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new)); } Index: includes/database/pgsql/database.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/pgsql/database.inc,v retrieving revision 1.3 diff -u -p -r1.3 database.inc --- includes/database/pgsql/database.inc 15 Sep 2008 20:48:07 -0000 1.3 +++ includes/database/pgsql/database.inc 24 Sep 2008 06:24:12 -0000 @@ -14,6 +14,13 @@ class DatabaseConnection_pgsql extends DatabaseConnection { protected $transactionSupport; + protected $concat_operator = '||'; + public $random = 'RANDOM()'; + public $substr = 'SUBSTRING'; + + public function ifNull($expr1, $expr2) { + return " COALESCE($expr1, $expr2) "; + } public function __construct(Array $connection_options = array()) { @@ -27,7 +34,10 @@ class DatabaseConnection_pgsql extends D $dsn .= ' port=' . $connection_options['port']; } - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array(PDO::ATTR_STRINGIFY_FETCHES => TRUE)); + parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( + // Convert numeric values to strings when fetching. + PDO::ATTR_STRINGIFY_FETCHES => TRUE, + )); } public function query($query, Array $args = array(), $options = array()) { @@ -77,7 +87,7 @@ class DatabaseConnection_pgsql extends D public function queryRange($query, Array $args, $from, $count, Array $options) { // Backward compatibility hack, temporary. - $query = str_replace(array('%d' , '%f' , '%b' , "'%s'"), '?', $query); + $query = str_replace(array('%n', '%d', '%f', '%b', "'%s'", '%s'), '?', $query); return $this->query($query . ' LIMIT ' . $count . ' OFFSET ' . $from, $args, $options); } Index: includes/database/pgsql/query.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/pgsql/query.inc,v retrieving revision 1.2 diff -u -p -r1.2 query.inc --- includes/database/pgsql/query.inc 15 Sep 2008 20:48:07 -0000 1.2 +++ includes/database/pgsql/query.inc 24 Sep 2008 06:24:12 -0000 @@ -71,7 +71,7 @@ class InsertQuery_pgsql extends InsertQu // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); - $query = "INSERT INTO {" . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; + $query = "INSERT INTO [{" . $this->table . '}] ([' . implode('], [', $insert_fields) . ']) VALUES '; $max_placeholder = 0; $values = array(); Index: includes/database/pgsql/schema.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/pgsql/schema.inc,v retrieving revision 1.2 diff -u -p -r1.2 schema.inc --- includes/database/pgsql/schema.inc 15 Sep 2008 20:48:07 -0000 1.2 +++ includes/database/pgsql/schema.inc 24 Sep 2008 06:24:12 -0000 @@ -39,15 +39,15 @@ class DatabaseSchema_pgsql extends Datab $sql_keys = array(); if (isset($table['primary key']) && is_array($table['primary key'])) { - $sql_keys[] = 'PRIMARY KEY (' . implode(', ', $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_keys[] = 'CONSTRAINT [{' . $name . '}_' . $key_name . '_key] UNIQUE ([' . implode('], [', $key) . '])'; } } - $sql = "CREATE TABLE {" . $name . "} (\n\t"; + $sql = "CREATE TABLE [{" . $name . "}] (\n\t"; $sql .= implode(",\n\t", $sql_fields); if (count($sql_keys) > 0) { $sql .= ",\n\t"; @@ -78,17 +78,33 @@ class DatabaseSchema_pgsql extends Datab * The field specification, as per the schema data structure format. */ protected function createFieldSql($name, $spec) { - $sql = $name . ' ' . $spec['pgsql_type']; + $sql = '[' . $name . '] ' . $spec['pgsql_type']; if ($spec['type'] == 'serial') { unset($spec['not null']); } + + // pgsql does not have unsigned types but supports constraints to + // restricted a signed field to be non-negative (e.g. CHECK (VALUE + // >= 0)). system.module defines {,small,big}int_unsigned as the + // corresponding integer type with this constraint but does not do + // so for serial or numeric types. It probably would have been + // cleaner to unify unsigned handling but, for now, we use the + // *int_unsigned types for int and otherwise apply a column + // constraint explicitly. if (!empty($spec['unsigned'])) { - if ($spec['type'] == 'serial') { - $sql .= " CHECK ($name >= 0)"; - } - else { - $sql .= '_unsigned'; + switch ($spec['type']) { + case 'int': + case 'serial': + case 'float': + $sql .= " CHECK ([$name] >= 0)"; + break; + case 'numeric': + // handled below + break; + default: + // unsigned is not supported on other column types + break; } } @@ -99,12 +115,17 @@ class DatabaseSchema_pgsql extends Datab $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')'; } + // For numeric columns this has to come after (precision,scale). + if ($spec['type'] == 'numeric' && !empty($spec['unsigned'])) { + $sql .= " CHECK ([$name] >= 0)"; + } + 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"; + $sql .= " DEFAULT $default"; } return $sql; @@ -141,40 +162,40 @@ class DatabaseSchema_pgsql extends Datab // it much easier for modules (such as schema.module) to map // database types back into schema types. $map = array( - 'varchar:normal' => 'varchar', - 'char:normal' => 'character', + 'varchar:normal' => 'VARCHAR', + 'char:normal' => 'CHARACTER', + + 'text:tiny' => 'TEXT', + 'text:small' => 'TEXT', + 'text:medium' => 'TEXT', + 'text:big' => 'TEXT', + 'text:normal' => 'TEXT', + + 'serial:tiny' => 'SERIAL', + 'serial:small' => 'SERIAL', + 'serial:medium' => 'SERIAL', + 'serial:big' => 'BIGSERIAL', + 'serial:normal' => 'SERIAL', + + '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', - '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', - ); + 'datetime:normal' => 'TIMESTAMP', + ); return $map; } @@ -182,10 +203,10 @@ class DatabaseSchema_pgsql extends Datab $ret = array(); foreach ($fields as $field) { if (is_array($field)) { - $ret[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')'; + $ret[] = 'SUBSTRING([' . $field[0] . '], 1, ' . $field[1] . ')'; } else { - $ret[] = $field; + $ret[] = '[' . $field . ']'; } } return implode(', ', $ret); @@ -202,7 +223,7 @@ class DatabaseSchema_pgsql extends Datab * The new name for the table. */ function renameTable(&$ret, $table, $new_name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] RENAME TO [{' . $new_name . '}]'); } /** @@ -214,7 +235,7 @@ class DatabaseSchema_pgsql extends Datab * The table to be dropped. */ public function dropTable(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {' . $table . '}'); + $ret[] = update_sql('DROP TABLE [{' . $table . '}]'); } /** @@ -246,17 +267,17 @@ class DatabaseSchema_pgsql extends Datab $fixnull = TRUE; $spec['not null'] = FALSE; } - $query = 'ALTER TABLE {' . $table . '} ADD COLUMN '; + $query = 'ALTER TABLE [{' . $table . '}] ADD COLUMN '; $query .= $this->_createFieldSql($field, $this->_processField($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']); + $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"); + $ret[] = update_sql("ALTER TABLE [{" . $table . "}] ALTER [$field] SET NOT NULL"); } if (isset($new_keys)) { $this->_createKeys($ret, $table, $new_keys); @@ -274,7 +295,7 @@ class DatabaseSchema_pgsql extends Datab * The field to be dropped. */ public function dropField(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP COLUMN ' . $field); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP COLUMN [' . $field . ']'); } /** @@ -297,7 +318,7 @@ class DatabaseSchema_pgsql extends Datab $default = is_string($default) ? "'$default'" : $default; } - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN ' . $field . ' SET DEFAULT ' . $default); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ALTER COLUMN [' . $field . '] SET DEFAULT ' . $default); } /** @@ -311,7 +332,7 @@ class DatabaseSchema_pgsql extends Datab * The field to be altered. */ public function fieldSetNoDefault(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN ' . $field . ' DROP DEFAULT'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ALTER COLUMN [' . $field . '] DROP DEFAULT'); } /** @@ -325,7 +346,7 @@ class DatabaseSchema_pgsql extends Datab * Fields for the primary key. */ public function addPrimaryKey(&$ret, $table, $fields) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ADD PRIMARY KEY ([' . implode('], [', $fields) . ')]'); } /** @@ -337,7 +358,7 @@ class DatabaseSchema_pgsql extends Datab * The table to be altered. */ public function dropPrimaryKey(&$ret, $table) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP CONSTRAINT {' . $table . '}_pkey'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP CONSTRAINT [{' . $table . '}_pkey]'); } /** @@ -354,7 +375,7 @@ class DatabaseSchema_pgsql extends Datab */ function addUniqueKey(&$ret, $table, $name, $fields) { $name = '{' . $table . '}_' . $name . '_key'; - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD CONSTRAINT ' . $name . ' UNIQUE (' . implode(',', $fields) . ')'); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] ADD CONSTRAINT [' . $name . '] UNIQUE ([' . implode('], [', $fields) . '])'); } /** @@ -369,7 +390,7 @@ class DatabaseSchema_pgsql extends Datab */ public function dropUniqueKey(&$ret, $table, $name) { $name = '{' . $table . '}_' . $name . '_key'; - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $name); + $ret[] = update_sql('ALTER TABLE [{' . $table . '}] DROP CONSTRAINT [' . $name . ']'); } /** @@ -400,7 +421,7 @@ class DatabaseSchema_pgsql extends Datab */ public function dropIndex(&$ret, $table, $name) { $name = '{' . $table . '}_' . $name . '_idx'; - $ret[] = update_sql('DROP INDEX ' . $name); + $ret[] = update_sql('DROP INDEX [' . $name . ']'); } /** @@ -466,16 +487,16 @@ class DatabaseSchema_pgsql extends Datab * table specification but without the 'fields' element. */ public function changeField(&$ret, $table, $field, $field_new, $spec, $new_keys = array()) { - $ret[] = update_sql("ALTER TABLE {" . $table . "} RENAME $field TO " . $field . "_old"); + $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']); $this->addField($ret, $table, "$field_new", $spec); - $ret[] = update_sql("UPDATE {" . $table . "} SET $field_new = " . $field . "_old"); + $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"); + $ret[] = update_sql("ALTER TABLE [{" . $table . "}] ALTER [$field_new] SET NOT NULL"); } $this->dropField($ret, $table, $field . '_old'); @@ -486,7 +507,7 @@ class DatabaseSchema_pgsql extends Datab } protected function _createIndexSql($table, $name, $fields) { - $query = 'CREATE INDEX {' . $table . '}_' . $name . '_idx ON {' . $table . '} ('; + $query = 'CREATE INDEX [{' . $table . '}_' . $name . '_idx] ON [{' . $table . '}] ('; $query .= $this->_createKeySql($fields) . ')'; return $query; }