diff options
Diffstat (limited to 'includes/database')
-rw-r--r-- | includes/database/database.inc | 28 | ||||
-rw-r--r-- | includes/database/pgsql/query.inc | 67 | ||||
-rw-r--r-- | includes/database/select.inc | 9 |
3 files changed, 102 insertions, 2 deletions
diff --git a/includes/database/database.inc b/includes/database/database.inc index 73cb243e5..ca6023f2d 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -810,6 +810,34 @@ abstract class DatabaseConnection extends PDO { } /** + * Escapes an alias name string. + * + * Force all alias names to be strictly alphanumeric-plus-underscore. In + * contrast to DatabaseConnection::escapeField() / + * DatabaseConnection::escapeTable(), this doesn't allow the period (".") + * + * @return + * The sanitized field name string. + */ + public function escapeAlias($field) { + return preg_replace('/[^A-Za-z0-9_]+/', '', $field); + } + + /** + * Escapes a alias name string. + * + * Force all alias names to be strictly alphanumeric-plus-underscore. In + * contrast to DatabaseConnection::escapeField() / + * DatabaseConnection::escapeTable(), this doesn't allow the point. + * + * @return + * The sanitized field name string. + */ + public function escapeAlias($field) { + return preg_replace('/[^A-Za-z0-9_]+/', '', $field); + } + + /** * Escapes characters that work as wildcard characters in a LIKE pattern. * * The wildcard characters "%" and "_" as well as backslash are prefixed with diff --git a/includes/database/pgsql/query.inc b/includes/database/pgsql/query.inc index 61c821801..7642f53f7 100644 --- a/includes/database/pgsql/query.inc +++ b/includes/database/pgsql/query.inc @@ -217,4 +217,71 @@ class SelectQuery_pgsql extends SelectQuery { return $this; } + /** + * Overrides SelectQuery::orderBy(). + * + * Automatically adds columns that are ordered on as fields. + */ + public function orderBy($field, $direction = 'ASC') { + // Call parent function to order on this. + $return = parent::orderBy($field, $direction); + + // PostgreSQL requires that when using DISTINCT or GROUP BY conditions, + // fields/expressions that are ordered on also need to be selected. + // This function tries to automatically add a field if it is not already + // added or a condition applies that makes it impossible to handle that + // automatically. In such cases, the query might fail on PostgreSQL if the + // field or expression is not added manually. + + // If there is a table alias specified, split it up. + if (strpos($field, '.') !== FALSE) { + list($table, $table_field) = explode('.', $field); + } + // Figure out if the field has already been added. + foreach ($this->fields as $existing_field) { + if (!empty($table)) { + // If table alias is given, check if field and table exists. + if ($existing_field['table'] == $table && $existing_field['field'] == $table_field) { + return $return; + } + } + else { + // If there is no table, simply check if the field exists as a field or + // an aliased field. + if ($existing_field['alias'] == $field) { + return $return; + } + } + } + + // Also check expression aliases. + foreach ($this->expressions as $expression) { + if ($expression['alias'] == $field) { + return $return; + } + } + + // If a table loads all fields, it can not be added again. It would + // result in an ambigious alias error because that field would be loaded + // twice: Once through table_alias.* and once directly. If the field + // actually belongs to a different table, it must be added manually. + foreach ($this->tables as $table) { + if (!empty($table['all_fields'])) { + return $return; + } + } + + // If $field contains an characters which are not allowed in a field name + // it is considered an expression, these can't be handeld automatically + // either. + if ($this->connection->escapeField($field) != $field) { + return $return; + } + + // This is a case that can be handled automatically, add the field. + $this->addField(NULL, $field); + return $return; + } + + } diff --git a/includes/database/select.inc b/includes/database/select.inc index d46abefdf..0fc17f586 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -366,6 +366,11 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn * If called multiple times, the query will order by each specified field in the * order this method is called. * + * If the query uses DISTINCT or GROUP BY conditions, fields or expressions + * that are used for the order must be selected to be compatible with some + * databases like PostgreSQL. The PostgreSQL driver can handle simple cases + * automatically but it is suggested to explicitly specify them. + * * @param $field * The field on which to order. * @param $direction @@ -1373,10 +1378,10 @@ class SelectQuery extends Query implements SelectQueryInterface { foreach ($this->fields as $alias => $field) { // Always use the AS keyword for field aliases, as some // databases require it (e.g., PostgreSQL). - $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeField($field['alias']); + $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']); } foreach ($this->expressions as $alias => $expression) { - $fields[] = $expression['expression'] . ' AS ' . $expression['alias']; + $fields[] = $expression['expression'] . ' AS ' . $this->connection->escapeAlias($expression['alias']); } $query .= implode(', ', $fields); |