As a security hardening, the automatic expansion of database query placeholders has been removed to require explicit intent by the developer to use an array of values.
A "normal" placeholder like :name will no longer be expanded to multiple placeholder if the value is an array. To support IN() syntax, we instead use a special placeholder format like :name[]. The brackets on the end are intended to evoke array syntax so it's visually clear that this will take an array of values.
In addition, when using db_select() it now requires the explicit passing of 'IN' to the ->condition() method instead of assuming it based on an array value, so that there is never unintended expansion.
Before:
db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => $vids))->fetchCol();
After:
db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids[]) AND th.parent = 0', array(':vids[]' => $vids))->fetchCol();
Before (where $tids is always an array):
$this->database->delete('taxonomy_term_hierarchy')
->condition('tid', $tids)
After (make explicit use of an IN condition):
$this->database->delete('taxonomy_term_hierarchy')
->condition('tid', $tids, 'IN')
If your code accepts both array or scalar values for the same query value (not a best practice), you should either use type-hinting to enforce an array, or you can cast to array:
Before:
// Value can be a string or array of strings.
$query->condition('l.' . $field, $value);
After:
// Cast scalars to array so we can consistently use an IN condition.
$query->condition('l.' . $field, (array) $value, 'IN');
Example of Drupal 7.x to 8.0.x update of db_query() with IN condition:
Drupal 7 from function drupal_lookup_path()
$cache['system_paths'] = $cached->data;
// Now fetch the aliases corresponding to these system paths.
$args = array(
':system' => $cache['system_paths'],
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
);
if ($path_language == LANGUAGE_NONE) {
// Prevent PDO from complaining about a token the query doesn't use.
unset($args[':language']);
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args);
}
elseif ($path_language < LANGUAGE_NONE) {
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args);
}
else {
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args);
}
$cache['map'][$path_language] = $result->fetchAllKeyed();
// Keep a record of paths with no alias to avoid querying twice.
$cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language])));
}
Drupal 8 from method \Drupal\Core\Path\AliasStorage::preloadPathAlias()
public function preloadPathAlias($preloaded, $langcode) {
$args = array(
':system[]' => $preloaded,
':langcode' => $langcode,
':langcode_undetermined' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
);
if ($langcode == LanguageInterface::LANGCODE_NOT_SPECIFIED) {
// Prevent PDO from complaining about a token the query doesn't use.
unset($args[':langcode']);
$result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system[]) AND langcode = :langcode_undetermined ORDER BY pid ASC', $args);
}
elseif ($langcode < LanguageInterface::LANGCODE_NOT_SPECIFIED) {
$result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system[]) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode ASC, pid ASC', $args);
}
else {
$result = $this->connection->query('SELECT source, alias FROM {url_alias} WHERE source IN (:system[]) AND langcode IN (:langcode, :langcode_undetermined) ORDER BY langcode DESC, pid ASC', $args);
}
return $result->fetchAllKeyed();
}