Drupal 7.14 API Compatibility Breaking Change

Minor Drupal versions are usually for bug fixes, security updates, and the occasional new feature that doesn't break backwards compatibility. Compatibility changes are reserved for major Drupal releases. There are exceptions such as Drupal 6.2. It was such a big deal there is an update documentation page just for this release. When Drupal 7.14 made an API breaking change without providing documentation or notification to module developers I was quite surprised. The lack of detail made it difficult to track down the changes when I had a broken codebase. Here are the details so others can, hopefully, have an easier time if they run into this problem.

What Is An API Breaking Change?

I consider a change to be API breaking if for the same input a function or method has a change to the output. For example, the data structure for the response from a function is different from before. This is what happened in Drupal 7.14.

The Change in Drupal 7.14

Prior to Drupal 7.14 the database connections were configured, by default, to fetch all field and column names as lowercase. As of Drupal 7.14 this has changed and they come back as the database sent them.

For many modules who have defined their schemas to use lowercase table names and fields this change won't cause an issue. If however a module makes a SQL query like show table status (using a function like db_query()) the results will no longer be lowercase. Say you get the results back as an associative array the key names will be different (e.g., Name instead of name). This has caused some modules to break.

For the record, I like this change in theory. Bringing it into the middle of the Drupal 7 life cycle without documenting the change or notifying the develops was a bad idea.

How To Work Around It

The following work around will work for all versions of Drupal 7 by generating lowercase field and table names. This should only be used for the places it's absolutely needed. Instead of using db_query use the following function:

function _custom_db_query($query, array $args = array(), array $options = array()) {
  if (empty($options['target'])) {
    $options['target'] = 'default';
  }

  $connection = Database::getConnection($options['target']);

  // Backup and restore PDO::ATTR_CASE. Prior to Drupal 7.14 this is
  // PDO::CASE_LOWER. Afterwards it is PDO::CASE_NATURAL.
  $orig = $connection->getAttribute(PDO::ATTR_CASE);
  $connection->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);

  // Execute the query statement.
  $result = $connection->queryRange($query, $from, $count, $args, $options);

  // Restore the attribute.
  $connection->setAttribute(PDO::ATTR_CASE, $orig);

  return $result;
}