diff --git a/composer.json b/composer.json
index 71c714f..ba000a3 100644
--- a/composer.json
+++ b/composer.json
@@ -32,8 +32,7 @@
   "autoload": {
     "psr-4": {
       "Drupal\\Core\\": "core/lib/Drupal/Core",
-      "Drupal\\Component\\": "core/lib/Drupal/Component",
-      "Drupal\\Driver\\": "drivers/lib/Drupal/Driver"
+      "Drupal\\Component\\": "core/lib/Drupal/Component"
     },
     "files": [
       "core/lib/Drupal.php"
diff --git a/core/drivers/fake/fake.info.yml b/core/drivers/fake/fake.info.yml
new file mode 100644
index 0000000..0c4fbd6
--- /dev/null
+++ b/core/drivers/fake/fake.info.yml
@@ -0,0 +1,5 @@
+# @todo Rename this driver from 'fake' to e.g. 'test_db_driver'.
+#   (\Drupal\fake\* lacks context.)
+type: driver
+core: 8.x
+name: Fake database driver for use in unit tests
diff --git a/core/drivers/fake/src/ConditionResolver.php b/core/drivers/fake/src/ConditionResolver.php
new file mode 100644
index 0000000..016d42d
--- /dev/null
+++ b/core/drivers/fake/src/ConditionResolver.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\ConditionResolver.
+ */
+
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\Query\Condition;
+
+class ConditionResolver {
+
+  /**
+   * Match a row against a group of conditions.
+   *
+   * @param \Drupal\fake\DatabaseRowInterface $row
+   *   The database row object.
+   * @param \Drupal\Core\Database\Query\Condition $condition_group
+   *   The condition group object.
+   *
+   * @return bool
+   *   TRUE if there is a match.
+   */
+  public static function matchGroup(DatabaseRowInterface $row, Condition $condition_group) {
+    $conditions = $condition_group->conditions();
+    $and = $conditions['#conjunction'] == 'AND';
+    unset($conditions['#conjunction']);
+    $match = TRUE;
+    foreach ($conditions as $condition) {
+      $match = $condition['field'] instanceof Condition ? static::matchGroup($row, $condition['field']) : static::matchSingle($row, $condition);
+      // For AND, finish matching on the first fail. For OR, finish on first
+      // success.
+      if ($and != $match) {
+        break;
+      }
+    }
+    return $match;
+  }
+
+  /**
+   * Match a single row and its condition.
+   *
+   * @param \Drupal\migrate\tests\DatabaseRowInterface $row
+   *   The row to match.
+   *
+   * @param array $condition
+   *   An array representing a single condition.
+   *
+   * @return bool
+   *   TRUE if the condition matches.
+   *
+   * @throws \Exception
+   */
+  protected static function matchSingle(DatabaseRowInterface $row, array $condition) {
+    $row_value = $row->getValue($condition['field']);
+    switch ($condition['operator']) {
+      case '=':
+        return $row_value == $condition['value'];
+
+      case '<=':
+        return $row_value <= $condition['value'];
+
+      case '>=':
+        return $row_value >= $condition['value'];
+
+      case '!=':
+        return $row_value != $condition['value'];
+
+      case '<>':
+        return $row_value != $condition['value'];
+
+      case '<':
+        return $row_value < $condition['value'];
+
+      case '>':
+        return $row_value > $condition['value'];
+
+      case 'IN':
+        return in_array($row_value, $condition['value']);
+
+      case 'IS NULL':
+        return !isset($row_value);
+
+      case 'IS NOT NULL':
+        return isset($row_value);
+
+      default:
+        throw new \Exception(sprintf('operator %s is not supported', $condition['operator']));
+    }
+  }
+
+}
diff --git a/core/drivers/fake/src/DatabaseRow.php b/core/drivers/fake/src/DatabaseRow.php
new file mode 100644
index 0000000..0bf7bfc
--- /dev/null
+++ b/core/drivers/fake/src/DatabaseRow.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\DatabaseRow.
+ */
+
+namespace Drupal\fake;
+
+class DatabaseRow implements DatabaseRowInterface {
+
+  /**
+   * Construct a new row.
+   *
+   * @param array $row
+   *   The row data.
+   */
+  public function __construct(array $row) {
+    $this->row = $row;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue($field) {
+    return $this->row[$field];
+  }
+
+}
diff --git a/core/drivers/fake/src/DatabaseRowInterface.php b/core/drivers/fake/src/DatabaseRowInterface.php
new file mode 100644
index 0000000..444a578
--- /dev/null
+++ b/core/drivers/fake/src/DatabaseRowInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\DatabaseRowInterface.
+ */
+
+namespace Drupal\fake;
+
+interface DatabaseRowInterface {
+
+  /**
+   * Get the field value from the row.
+   *
+   * @param mixed $field
+   *   The field to get the value of.
+   *
+   * @return mixed
+   *   The field value from the row.
+   */
+  public function getValue($field);
+}
diff --git a/core/drivers/fake/src/DatabaseRowSelect.php b/core/drivers/fake/src/DatabaseRowSelect.php
new file mode 100644
index 0000000..c7479c6
--- /dev/null
+++ b/core/drivers/fake/src/DatabaseRowSelect.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\DatabaseRowSelect.
+ */
+
+namespace Drupal\fake;
+
+class DatabaseRowSelect extends DatabaseRow {
+
+  /**
+   * Construct a new database row.
+   *
+   * @param array $row
+   *   The row data.
+   * @param array $fields_with_table
+   *   The fields with a table.
+   * @param array $fields
+   *   The fields.
+   */
+  public function __construct(array $row, array $fields_with_table, array $fields) {
+    $this->fieldsWithTable = $fields_with_table;
+    $this->fields = $fields;
+    parent::__construct($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue($field) {
+    $field_info = isset($this->fieldsWithTable[$field]) ? $this->fieldsWithTable[$field] : $this->fields[$field];
+    if (array_key_exists($field_info['field'], $this->row[$field_info['table']]['result'])) {
+      $index = 'result';
+    }
+    else {
+      $index = 'all';
+    }
+    return $this->row[$field_info['table']][$index][$field_info['field']];
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeConnection.php b/core/drivers/fake/src/FakeConnection.php
new file mode 100644
index 0000000..c97f67e
--- /dev/null
+++ b/core/drivers/fake/src/FakeConnection.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeConnection.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\Connection;
+
+/**
+ * Defines a fake connection for use during testing.
+ */
+class FakeConnection extends Connection {
+
+  /**
+   * Table prefix on the database.
+   *
+   * @var string
+   */
+  protected $tablePrefix;
+
+  /**
+   * Connection options for the database.
+   *
+   * @var array
+   */
+  protected $connectionOptions;
+
+  /**
+   * Constructs a FakeConnection.
+   *
+   * @param array $database_contents
+   *   The database contents faked as an array. Each key is a table name, each
+   *   value is a list of table rows.
+   * @param array $connection_options
+   *   (optional) The array of connection options for the database.
+   * @param string $prefix
+   *   (optional) The table prefix on the database.
+   */
+  public function __construct(array $database_contents, $connection_options = array(), $prefix = '') {
+    $this->databaseContents = $database_contents;
+    $this->connectionOptions = $connection_options;
+    $this->tablePrefix = $prefix;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConnectionOptions() {
+    return $this->connectionOptions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tablePrefix($table = 'default') {
+    return $this->tablePrefix;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function select($table, $alias = NULL, array $options = array()) {
+    return new FakeSelect($this->databaseContents, $table, $alias);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function insert($table, array $options = array()) {
+    return new FakeInsert($this->databaseContents, $table);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function merge($table, array $options = array()) {
+    return new FakeMerge($this->databaseContents, $table);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function update($table, array $options = array()) {
+    return new FakeUpdate($this->databaseContents, $table);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function truncate($table, array $options = array()) {
+    return new FakeTruncate($this->databaseContents, $table);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function schema() {
+    return new FakeDatabaseSchema($this->databaseContents);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query($query, array $args = array(), $options = array()) {
+    throw new \Exception('Method not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
+    throw new \Exception('Method not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function queryTemporary($query, array $args = array(), array $options = array()) {
+    throw new \Exception('Method not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function driver() {
+    throw new \Exception('Method not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function databaseType() {
+    throw new \Exception('Method not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createDatabase($database) {
+    // There is nothing to do.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mapConditionOperator($operator) {
+    throw new \Exception('Method not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function nextId($existing_id = 0) {
+    throw new \Exception('Method not supported');
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeDatabaseSchema.php b/core/drivers/fake/src/FakeDatabaseSchema.php
new file mode 100644
index 0000000..c287f04
--- /dev/null
+++ b/core/drivers/fake/src/FakeDatabaseSchema.php
@@ -0,0 +1,248 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeDatabaseSchema.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\Schema;
+
+class FakeDatabaseSchema extends Schema {
+
+  /**
+   * As set on MigrateSqlSourceTestCase::databaseContents.
+   *
+   * @var array
+   */
+  public $databaseContents;
+
+  /**
+   * Constructs a fake database schema.
+   *
+   * @param array $database_contents
+   *   The database contents faked as an array. Each key is a table name, each
+   *   value is a list of table rows.
+   */
+  public function __construct(array &$database_contents) {
+    $this->uniqueIdentifier = uniqid('', TRUE);
+
+    // @todo Maybe we can generate an internal representation.
+    $this->databaseContents = &$database_contents;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tableExists($table) {
+    return in_array($table, array_keys($this->databaseContents));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prefixNonTable($table) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addField($table, $field, $spec, $keys_new = array()) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addIndex($table, $name, $fields) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addPrimaryKey($table, $fields) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addUniqueKey($table, $name, $fields) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * PHP magic __clone() method.
+   */
+  public function __clone() {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function copyTable($source, $destination) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createTable($name, $table) {
+    #throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dropField($table, $field) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dropIndex($table, $name) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dropPrimaryKey($table) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dropTable($table) {
+    unset($this->databaseContents[$table]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dropUniqueKey($table, $name) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldExists($table, $column) {
+    if (!empty($this->databaseContents[$table])) {
+      $row = reset($this->databaseContents[$table]);
+      return isset($row[$column]);
+    }
+    else {
+      throw new \Exception("Can't determine whether field exists with an empty / nonexistent table.");
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldNames($fields) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldSetDefault($table, $field, $default) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fieldSetNoDefault($table, $field) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findTables($table_expression) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldTypeMap() {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function indexExists($table, $name) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function nextPlaceholder() {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareComment($comment, $length = NULL) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function renameTable($table, $new_name) {
+    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uniqueIdentifier() {
+    return $this->uniqueIdentifier;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Fake database schema',
+      'description' => 'Tests for fake database schema plugin.',
+      'group' => 'Migrate',
+    );
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeInsert.php b/core/drivers/fake/src/FakeInsert.php
new file mode 100644
index 0000000..9756983
--- /dev/null
+++ b/core/drivers/fake/src/FakeInsert.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeInsert.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\Query\Insert;
+use Drupal\Core\Database\Query\SelectInterface;
+
+/**
+ * Defines FakeInsert for use in database tests.
+ */
+class FakeInsert extends Insert {
+
+  /**
+   * The database contents.
+   *
+   * @var array
+   */
+  protected $databaseContents;
+
+  /**
+   * The database table to insert into.
+   *
+   * @var string
+   */
+  protected $table;
+
+  /**
+   * Constructs a fake insert object.
+   *
+   * @param array $database_contents
+   *   The database contents faked as an array. Each key is a table name, each
+   *   value is a list of table rows.
+   * @param string $table
+   *   The table to insert into.
+   * @param array $options
+   *   (optional) The database options. Not used.
+   */
+  public function __construct(array &$database_contents, $table, array $options = array()) {
+    $this->databaseContents = &$database_contents;
+    $this->table = $table;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function useDefaults(array $fields) {
+    throw new \Exception('This method is not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function from(SelectInterface $query) {
+    throw new \Exception('This method is not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    foreach ($this->insertValues as $values) {
+      $this->databaseContents[$this->table][] = array_combine($this->insertFields, $values);
+    }
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeMerge.php b/core/drivers/fake/src/FakeMerge.php
new file mode 100644
index 0000000..550243c
--- /dev/null
+++ b/core/drivers/fake/src/FakeMerge.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeMerge.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\Query\InvalidMergeQueryException;
+use Drupal\Core\Database\Query\Merge;
+
+/**
+ * Defines FakeMerge for use in database tests.
+ */
+class FakeMerge extends Merge {
+
+  /**
+   * Constructs a fake merge object and initializes the conditions.
+   *
+   * @param array $database_contents
+   *   The database contents faked as an array. Each key is a table name, each
+   *   value is a list of table rows.
+   * @param string $table
+   *   The database table to merge into.
+   */
+  public function __construct(array &$database_contents, $table) {
+    $this->databaseContents = &$database_contents;
+    $this->table = $table;
+    $this->conditionTable = $table;
+    $this->condition = new Condition('AND');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    if (!count($this->condition)) {
+      throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
+    }
+    $select = new FakeSelect($this->databaseContents, $this->conditionTable, 'c');
+    $count = $select
+      ->condition($this->condition)
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    if ($count) {
+      $update = new FakeUpdate($this->databaseContents, $this->table);
+      $update
+        ->fields($this->updateFields)
+        ->condition($this->condition)
+        ->execute();
+      return self::STATUS_UPDATE;
+    }
+    $insert = new FakeInsert($this->databaseContents, $this->table);
+    $insert->fields($this->insertFields)->execute();
+    return self::STATUS_INSERT;
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeSelect.php b/core/drivers/fake/src/FakeSelect.php
new file mode 100644
index 0000000..c6bd19b
--- /dev/null
+++ b/core/drivers/fake/src/FakeSelect.php
@@ -0,0 +1,540 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeSelect.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\Query\PlaceholderInterface;
+use Drupal\Core\Database\Query\Select;
+use Drupal\Core\Database\Query\SelectInterface;
+
+class FakeSelect extends Select {
+
+  /**
+   * Contents of the pseudo-database.
+   *
+   * Keys are table names and values are arrays of rows in the table.
+   * Every row there contains all table field values keyed by field name.
+   *
+   * @code
+   * array(
+   *   'user' => array(
+   *     array(
+   *       'uid' => 1,
+   *       'name' => 'admin',
+   *     ),
+   *     array(
+   *       'uid' => 2,
+   *       'name' => 'alice',
+   *     ),
+   *   ),
+   *   'node' => array(
+   *     array(
+   *       'nid' => 1,
+   *     )
+   *   )
+   * )
+   * @endcode
+   *
+   * @var array
+   */
+  protected $databaseContents;
+
+  protected $countQuery = FALSE;
+  protected $fieldsWithTable = array();
+
+  /**
+   * Constructs a new FakeSelect.
+   *
+   * @param array $database_contents
+   *   An array of mocked database content.
+   * @param string $table
+   *   The base table name used within fake select.
+   * @param string $alias
+   *   The base table alias used within fake select.
+   * @param string $conjunction
+   *   The operator to use to combine conditions: 'AND' or 'OR'.
+   */
+  public function __construct(array $database_contents, $table, $alias, $conjunction = 'AND') {
+    $this->databaseContents = $database_contents;
+    $this->addJoin(NULL, $table, $alias);
+    $this->where = new Condition($conjunction);
+    $this->having = new Condition($conjunction);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    return $this->addJoin('LEFT', $table, $alias, $condition, $arguments);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    if ($table instanceof SelectInterface) {
+      // @todo implement this.
+      throw new \Exception('Subqueries are not supported at this moment.');
+    }
+    $alias = parent::addJoin($type, $table, $alias, $condition, $arguments);
+    if (isset($type)) {
+      if ($type != 'INNER' && $type != 'LEFT') {
+        throw new \Exception(sprintf('%s type not supported, only INNER and LEFT.', $type));
+      }
+      if (!preg_match('/^(\w+\.)?(\w+)\s*=\s*(\w+)\.(\w+)$/', $condition, $matches)) {
+        throw new \Exception('Only x.field1 = y.field2 conditions are supported.' . $condition);
+      }
+      if (!$matches[1] && count($this->tables) == 2) {
+        $aliases = array_keys($this->tables);
+        $matches[1] = $aliases[0];
+      }
+      else {
+        $matches[1] = substr($matches[1], 0, -1);
+      }
+      if (!$matches[1]) {
+        throw new \Exception('Only x.field1 = y.field2 conditions are supported.' . $condition);
+      }
+      if ($matches[1] == $alias) {
+        $this->tables[$alias] += array(
+          'added_field' => $matches[2],
+          'original_table_alias' => $matches[3],
+          'original_field' => $matches[4],
+        );
+      }
+      elseif ($matches[3] == $alias) {
+        $this->tables[$alias] += array(
+          'added_field' => $matches[4],
+          'original_table_alias' => $matches[1],
+          'original_field' => $matches[2],
+        );
+      }
+      else {
+        throw new \Exception('The JOIN condition does not contain the alias of the joined table.');
+      }
+    }
+    return $alias;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    // @todo: Implement distinct() handling.
+
+    // Single table count queries often do not contain fields which this class
+    // does not support otherwise, so add a shortcut.
+    if (count($this->tables) == 1 && $this->countQuery) {
+      $table_info = reset($this->tables);
+      $where = $this->where;
+      if (!empty($this->databaseContents[$table_info['table']])) {
+        $results = array_filter($this->databaseContents[$table_info['table']], function ($row_array) use ($where) {
+          return ConditionResolver::matchGroup(new DatabaseRow($row_array), $where);
+        });
+      }
+      else {
+        $results = array();
+      }
+    }
+    else {
+      $all_rows = $this->executeJoins();
+      $all_rows = $this->resolveConditions($this->where, $all_rows);
+      if (!empty($this->order)) {
+        usort($all_rows, array($this, 'sortCallback'));
+      }
+      // Now flatten the rows so that each row becomes a field alias => value
+      // array.
+      $results = array();
+      foreach ($all_rows as $table_rows) {
+        $result_row = array();
+        foreach ($table_rows as $row) {
+          $result_row += $row['result'];
+        }
+        $results[] = $result_row;
+      }
+    }
+    if (!empty($this->range)) {
+      $results = array_slice($results, $this->range['start'], $this->range['length']);
+    }
+    if ($this->countQuery) {
+      $results = array(array(count($results)));
+    }
+    return new FakeStatement($results);
+  }
+
+  /**
+   * Create an initial result set by executing the joins and picking fields.
+   *
+   * @return array
+   *   A multidimensional array, the first key are table aliases, the second
+   *   are field aliases, the values are the database contents or NULL in case
+   *   of JOINs.
+   */
+  protected function executeJoins() {
+    $fields = array();
+    foreach ($this->fields as $field_info) {
+      $this->fieldsWithTable[$field_info['table'] . '.' . $field_info['field']] = $field_info;
+      $fields[$field_info['table']][$field_info['field']] = NULL;
+    }
+    foreach ($this->tables as $alias => $table_info) {
+      if ($table = reset($this->databaseContents[$table_info['table']])) {
+        foreach (array_keys($table) as $field) {
+          if (!isset($this->fields[$field])) {
+            $this->fieldsWithTable[$field] = array(
+              'table' => $alias,
+              'field' => $field,
+            );
+            $this->fieldsWithTable["$alias.$field"] = array(
+              'table' => $alias,
+              'field' => $field,
+            );
+          }
+        }
+      }
+    }
+    // This will contain a multiple dimensional array. The first key will be a
+    // table alias, the second either result or all, the third will be a field
+    // alias. all contains every field in the table with the original field
+    // names while result contains only the fields requested. This allows for
+    // filtering on fields that were not added via addField().
+    $results = array();
+    foreach ($this->tables as $table_alias => $table_info) {
+      // The base table for this query.
+      if (empty($table_info['join type'])) {
+        foreach ($this->databaseContents[$table_info['table']] as $candidate_row) {
+          $results[] = $this->getNewRow($table_alias, $fields, $candidate_row);
+        }
+      }
+      else {
+        $new_rows = array();
+
+        // Dynamically build a set of joined rows. Check existing rows and see
+        // if they can be joined with incoming rows.
+        foreach ($results as $row) {
+          $joined = FALSE;
+          foreach ($this->databaseContents[$table_info['table']] as $candidate_row) {
+            if ($row[$table_info['original_table_alias']]['result'][$table_info['original_field']] == $candidate_row[$table_info['added_field']]) {
+              $joined = TRUE;
+              $new_rows[] = $this->getNewRow($table_alias, $fields, $candidate_row, $row);
+            }
+          }
+          if (!$joined && $table_info['join type'] == 'LEFT') {
+            // Because PHP doesn't scope their foreach statements,
+            // $candidate_row may contain the last value assigned to it from the
+            // previous statement.
+            // @TODO: empty tables? Those are a problem.
+            $keys = array_keys($candidate_row);
+            $values = array_fill(0, count($keys), NULL);
+            $new_row = array(
+              'result' => $fields[$table_alias],
+              'all' => array_combine($keys, $values),
+            );
+            $new_rows[] = array($table_alias => $new_row) + $row;
+          }
+        }
+        $results = $new_rows;
+      }
+    }
+    return $results;
+  }
+
+  /**
+   * Retrieves a new row.
+   *
+   * @param string $table_alias
+   * @param array $fields
+   * @param array $candidate_row
+   * @param array $row
+   *
+   * @return array
+   */
+  protected function getNewRow($table_alias, $fields, $candidate_row, $row = array()) {
+    $new_row[$table_alias]['all'] = $candidate_row;
+    foreach ($fields[$table_alias] as $field => $v) {
+      $new_row[$table_alias]['result'][$field] = $candidate_row[$field];
+    }
+    return $new_row + $row;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function countQuery() {
+    $query = clone $this;
+    return $query->setCountQuery();
+  }
+
+  /**
+   * Set this query to be a count query.
+   */
+  protected function setCountQuery() {
+    $this->countQuery = TRUE;
+    return $this;
+  }
+
+  /**
+   * usort callback to order the results.
+   */
+  protected function sortCallback($a, $b) {
+    $a_row = new DatabaseRowSelect($a, $this->fieldsWithTable, $this->fields);
+    $b_row = new DatabaseRowSelect($b, $this->fieldsWithTable, $this->fields);
+    foreach ($this->order as $field => $direction) {
+      $a_value = $a_row->getValue($field);
+      $b_value = $b_row->getValue($field);
+      if ($a_value != $b_value) {
+        return (($a_value < $b_value) == ($direction == 'ASC')) ? -1 : 1;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Resolves conditions by removing non-matching rows.
+   *
+   * @param \Drupal\Core\Database\Query\Condition $condition_group
+   *   The condition group to check.
+   * @param array $rows
+   *   An array of rows excluding non-matching rows.
+   *
+   * @return \Drupal\fake\ConditionResolver
+   *   The condition resolver object.
+   */
+  protected function resolveConditions(Condition $condition_group, array &$rows) {
+    $fields_with_table = $this->fieldsWithTable;
+    $fields = $this->fields;
+    return array_filter($rows, function ($row_array) use ($condition_group, $fields_with_table, $fields) {
+      $row = new DatabaseRowSelect($row_array, $fields_with_table, $fields);
+      return ConditionResolver::matchGroup($row, $condition_group);
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function orderBy($field, $direction = 'ASC') {
+    $this->order[$field] = strtoupper($direction);
+    return $this;
+  }
+
+  // ================== we could support these.
+  /**
+   * {@inheritdoc}
+   */
+  public function groupBy($field) {
+    // @todo: Implement groupBy() method.
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function havingCondition($field, $value = NULL, $operator = NULL) {
+    // @todo: Implement havingCondition() method.
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function uniqueIdentifier() {
+    // TODO: Implement uniqueIdentifier() method.
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  // ================== the rest won't be supported, ever.
+  /**
+   * {@inheritdoc}
+   */
+  public function nextPlaceholder() {
+    // TODO: Implement nextPlaceholder() method.
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isPrepared() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preExecute(SelectInterface $query = NULL) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function where($snippet, $args = array()) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extend($extender_name) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getExpressions() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getGroupBy() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getUnion() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function forUpdate($set = TRUE) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &conditions() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function orderRandom() {
+    // We could implement this but why bother.
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function union(SelectInterface $query, $type = '') {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addExpression($expression, $alias = NULL, $arguments = array()) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getTables() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getArguments(PlaceholderInterface $query_place_holder = NULL) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getOrderBy() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function &getFields() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function exists(SelectInterface $select) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function notExists(SelectInterface $select) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function arguments() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function compile(Connection $connection, PlaceholderInterface $query_place_holder) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function compiled() {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Fake select test',
+      'description' => 'Tests for fake select plugin.',
+      'group' => 'Migrate',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields($table_alias, array $fields = array()) {
+    if (!$fields) {
+      $table = $this->tables[$table_alias]['table'];
+      if (!empty($this->databaseContents[$table])) {
+        $fields = array_keys(reset($this->databaseContents[$table]));
+      }
+      else {
+        throw new \Exception(String::format('All fields on empty table @table is not supported.', array('@table' => $table)));
+      }
+    }
+    return parent::fields($table_alias, $fields);
+  }
+}
diff --git a/core/drivers/fake/src/FakeStatement.php b/core/drivers/fake/src/FakeStatement.php
new file mode 100644
index 0000000..a8aafea
--- /dev/null
+++ b/core/drivers/fake/src/FakeStatement.php
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeStatement.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\RowCountException;
+use Drupal\Core\Database\StatementInterface;
+
+/**
+ * Represents a fake prepared statement.
+ */
+class FakeStatement extends \ArrayIterator implements StatementInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($args = array(), $options = array()) {
+    throw new \Exception('This method is not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQueryString() {
+    throw new \Exception('This method is not supported');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rowCount() {
+    throw new RowCountException();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchField($index = 0) {
+    $row = array_values($this->current());
+    $return = $row[$index];
+    $this->next();
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchAssoc() {
+    $return = FALSE;
+    if ($this->valid()) {
+      $return = $this->current();
+      $this->next();
+    }
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchCol($index = 0) {
+    $return = array();
+    foreach ($this as $row) {
+      $row = array_values($row);
+      $return[] = $row[$index];
+    }
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchAllKeyed($key_index = 0, $value_index = 1) {
+    $return = array();
+    foreach ($this as $row) {
+      $row = array_values($row);
+      $return[$row[$key_index]] = $row[$value_index];
+    }
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchAllAssoc($key, $fetch = NULL) {
+    $return = array();
+    foreach ($this as $row) {
+      $return[$row[$key]] = $row;
+    }
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchObject() {
+    $return = $this->fetchAssoc();
+    return $return === FALSE ? FALSE : (object) $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Fake statement test',
+      'description' => 'Tests for fake statement plugin.',
+      'group' => 'Migrate',
+    );
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeTruncate.php b/core/drivers/fake/src/FakeTruncate.php
new file mode 100644
index 0000000..358e638
--- /dev/null
+++ b/core/drivers/fake/src/FakeTruncate.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeTruncate.
+ */
+
+namespace Drupal\fake;
+
+/**
+ * Defines FakeTruncate for use in database tests.
+ */
+class FakeTruncate {
+
+  /**
+   * Constructs a FakeTruncate object.
+   *
+   * @param array $database_contents
+   *   The database contents faked as an array. Each key is a table name, each
+   *   value is a list of table rows.
+   * @param string $table
+   *   The table to truncate.
+   */
+  public function __construct(array &$database_contents, $table) {
+    $this->databaseContents = &$database_contents;
+    $this->table = $table;
+  }
+
+  /**
+   * Executes the TRUNCATE query.
+   */
+  public function execute() {
+    $this->databaseContents[$this->table] = array();
+  }
+
+}
diff --git a/core/drivers/fake/src/FakeUpdate.php b/core/drivers/fake/src/FakeUpdate.php
new file mode 100644
index 0000000..c817309
--- /dev/null
+++ b/core/drivers/fake/src/FakeUpdate.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\fake\FakeUpdate.
+ */
+
+namespace Drupal\fake;
+
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Database\Query\Update;
+
+/**
+ * Defines FakeUpdate for use in database tests.
+ */
+class FakeUpdate extends Update {
+
+  /**
+   * The database table to update.
+   *
+   * @var string
+   */
+  protected $table;
+
+  /**
+   * The database contents.
+   *
+   * @var array
+   */
+  protected $databaseContents;
+
+  /**
+   * Constructs a FakeUpdate object and initializes the condition.
+   *
+   * @param array $database_contents
+   *   The database contents faked as an array. Each key is a table name, each
+   *   value is a list of table rows.
+   * @param string $table
+   *   The table to update.
+   */
+  public function __construct(array &$database_contents, $table) {
+    $this->databaseContents = &$database_contents;
+    $this->table = $table;
+    $this->condition = new Condition('AND');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $affected = 0;
+    if (isset($this->databaseContents[$this->table])) {
+      $fields = $this->fields;
+      $condition = $this->condition;
+      array_walk($this->databaseContents[$this->table], function (&$row_array) use ($fields, $condition, &$affected) {
+        $row = new DatabaseRow($row_array);
+        if (ConditionResolver::matchGroup($row, $condition)) {
+          $row_array = $fields + $row_array;
+          $affected++;
+        }
+      });
+    }
+    return $affected;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function exists(SelectInterface $select) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function where($snippet, $args = array()) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function expression($field, $expression, array $arguments = NULL) {
+    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
+  }
+
+}
diff --git a/core/drivers/fake/src/Install/Tasks.php b/core/drivers/fake/src/Install/Tasks.php
new file mode 100644
index 0000000..709a205
--- /dev/null
+++ b/core/drivers/fake/src/Install/Tasks.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\fake\Install\Tasks
+ */
+
+namespace Drupal\fake\Install;
+
+use Drupal\Core\Database\Install\Tasks as InstallTasks;
+
+/**
+ * Usually used to specify installation tasks but here we're only interested
+ * in setting $error to TRUE.
+ */
+class Tasks extends InstallTasks {
+  /**
+   * Prevent the installer from recognising this as a potential database driver.
+   * @TODO Looks like this is needed only if we define $pdoDriver to something valid e.g. mysql.
+   * Not sure we need to do that. We may not need this file at all?
+   *
+   * @var boolean
+   */
+  protected $error = TRUE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function name() {
+    return t('Fake database connection for use in unit tests');
+  }
+}
diff --git a/core/drivers/mysql/mysql.info.yml b/core/drivers/mysql/mysql.info.yml
new file mode 100644
index 0000000..b6939d9
--- /dev/null
+++ b/core/drivers/mysql/mysql.info.yml
@@ -0,0 +1,3 @@
+type: driver
+core: 8.x
+name: MySQL, MariaDB, Percona Server, or equivalent
diff --git a/core/drivers/mysql/src/Connection.php b/core/drivers/mysql/src/Connection.php
new file mode 100644
index 0000000..4c7d07f
--- /dev/null
+++ b/core/drivers/mysql/src/Connection.php
@@ -0,0 +1,275 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Connection
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseNotFoundException;
+use Drupal\Core\Database\TransactionCommitFailedException;
+use Drupal\Core\Database\DatabaseException;
+use Drupal\Core\Database\Connection as DatabaseConnection;
+
+/**
+ * @addtogroup database
+ * @{
+ */
+
+class Connection extends DatabaseConnection {
+
+  /**
+   * Error code for "Unknown database" error.
+   */
+  const DATABASE_NOT_FOUND = 1049;
+
+  /**
+   * Flag to indicate if the cleanup function in __destruct() should run.
+   *
+   * @var boolean
+   */
+  protected $needsCleanup = FALSE;
+
+  /**
+   * Constructs a Connection object.
+   */
+  public function __construct(\PDO $connection, array $connection_options = array()) {
+    parent::__construct($connection, $connection_options);
+
+    // This driver defaults to transaction support, except if explicitly passed FALSE.
+    $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
+
+    // MySQL never supports transactional DDL.
+    $this->transactionalDDLSupport = FALSE;
+
+    $this->connectionOptions = $connection_options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function open(array &$connection_options = array()) {
+    // The DSN should use either a socket or a host/port.
+    if (isset($connection_options['unix_socket'])) {
+      $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
+    }
+    else {
+      // Default to TCP connection on port 3306.
+      $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
+    }
+    if (!empty($connection_options['database'])) {
+      $dsn .= ';dbname=' . $connection_options['database'];
+    }
+    // Allow PDO options to be overridden.
+    $connection_options += array(
+      'pdo' => array(),
+    );
+    $connection_options['pdo'] += array(
+      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+      // So we don't have to mess around with cursors and unbuffered queries by default.
+      \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
+      // Make sure MySQL returns all matched rows on update queries including
+      // rows that actually didn't have to be updated because the values didn't
+      // change. This matches common behavior among other database systems.
+      \PDO::MYSQL_ATTR_FOUND_ROWS => TRUE,
+      // Because MySQL's prepared statements skip the query cache, because it's dumb.
+      \PDO::ATTR_EMULATE_PREPARES => TRUE,
+    );
+
+    $pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
+
+    // Force MySQL to use the UTF-8 character set. Also set the collation, if a
+    // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
+    // for UTF-8.
+    if (!empty($connection_options['collation'])) {
+      $pdo->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']);
+    }
+    else {
+      $pdo->exec('SET NAMES utf8');
+    }
+
+    // Set MySQL init_commands if not already defined.  Default Drupal's MySQL
+    // behavior to conform more closely to SQL standards.  This allows Drupal
+    // to run almost seamlessly on many different kinds of database systems.
+    // These settings force MySQL to behave the same as postgresql, or sqlite
+    // in regards to syntax interpretation and invalid data handling.  See
+    // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
+    // changed the meaning of TRADITIONAL we need to spell out the modes one by
+    // one.
+    $connection_options += array(
+      'init_commands' => array(),
+    );
+    $connection_options['init_commands'] += array(
+      'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
+    );
+    // Set connection options.
+    $pdo->exec(implode('; ', $connection_options['init_commands']));
+
+    return $pdo;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function serialize() {
+    // Cleanup the connection, much like __destruct() does it as well.
+    if ($this->needsCleanup) {
+      $this->nextIdDelete();
+    }
+    $this->needsCleanup = FALSE;
+
+    return parent::serialize();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __destruct() {
+    if ($this->needsCleanup) {
+      $this->nextIdDelete();
+    }
+  }
+
+  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
+    return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
+  }
+
+  public function queryTemporary($query, array $args = array(), array $options = array()) {
+    $tablename = $this->generateTemporaryTableName();
+    $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
+    return $tablename;
+  }
+
+  public function driver() {
+    return 'mysql';
+  }
+
+  public function databaseType() {
+    return 'mysql';
+  }
+
+  /**
+   * Overrides \Drupal\Core\Database\Connection::createDatabase().
+   *
+   * @param string $database
+   *   The name of the database to create.
+   *
+   * @throws \Drupal\Core\Database\DatabaseNotFoundException
+   */
+  public function createDatabase($database) {
+    // Escape the database name.
+    $database = Database::getConnection()->escapeDatabase($database);
+
+    try {
+      // Create the database and set it as active.
+      $this->connection->exec("CREATE DATABASE $database");
+      $this->connection->exec("USE $database");
+    }
+    catch (\Exception $e) {
+      throw new DatabaseNotFoundException($e->getMessage());
+    }
+  }
+
+  public function mapConditionOperator($operator) {
+    // We don't want to override any of the defaults.
+    return NULL;
+  }
+
+  public function nextId($existing_id = 0) {
+    $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
+    // This should only happen after an import or similar event.
+    if ($existing_id >= $new_id) {
+      // If we INSERT a value manually into the sequences table, on the next
+      // INSERT, MySQL will generate a larger value. However, there is no way
+      // of knowing whether this value already exists in the table. MySQL
+      // provides an INSERT IGNORE which would work, but that can mask problems
+      // other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY
+      // UPDATE in such a way that the UPDATE does not do anything. This way,
+      // duplicate keys do not generate errors but everything else does.
+      $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id));
+      $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
+    }
+    $this->needsCleanup = TRUE;
+    return $new_id;
+  }
+
+  public function nextIdDelete() {
+    // While we want to clean up the table to keep it up from occupying too
+    // much storage and memory, we must keep the highest value in the table
+    // because InnoDB  uses an in-memory auto-increment counter as long as the
+    // server runs. When the server is stopped and restarted, InnoDB
+    // reinitializes the counter for each table for the first INSERT to the
+    // table based solely on values from the table so deleting all values would
+    // be a problem in this case. Also, TRUNCATE resets the auto increment
+    // counter.
+    try {
+      $max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField();
+      // We know we are using MySQL here, no need for the slower db_delete().
+      $this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id));
+    }
+    // During testing, this function is called from shutdown with the
+    // simpletest prefix stored in $this->connection, and those tables are gone
+    // by the time shutdown is called so we need to ignore the database
+    // errors. There is no problem with completely ignoring errors here: if
+    // these queries fail, the sequence will work just fine, just use a bit
+    // more database storage and memory.
+    catch (DatabaseException $e) {
+    }
+  }
+
+  /**
+   * Overridden to work around issues to MySQL not supporting transactional DDL.
+   */
+  protected function popCommittableTransactions() {
+    // Commit all the committable layers.
+    foreach (array_reverse($this->transactionLayers) as $name => $active) {
+      // Stop once we found an active transaction.
+      if ($active) {
+        break;
+      }
+
+      // If there are no more layers left then we should commit.
+      unset($this->transactionLayers[$name]);
+      if (empty($this->transactionLayers)) {
+        if (!$this->connection->commit()) {
+          throw new TransactionCommitFailedException();
+        }
+      }
+      else {
+        // Attempt to release this savepoint in the standard way.
+        try {
+          $this->query('RELEASE SAVEPOINT ' . $name);
+        }
+        catch (DatabaseExceptionWrapper $e) {
+          // However, in MySQL (InnoDB), savepoints are automatically committed
+          // when tables are altered or created (DDL transactions are not
+          // supported). This can cause exceptions due to trying to release
+          // savepoints which no longer exist.
+          //
+          // To avoid exceptions when no actual error has occurred, we silently
+          // succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
+          if ($e->getPrevious()->errorInfo[1] == '1305') {
+            // If one SAVEPOINT was released automatically, then all were.
+            // Therefore, clean the transaction stack.
+            $this->transactionLayers = array();
+            // We also have to explain to PDO that the transaction stack has
+            // been cleaned-up.
+            $this->connection->commit();
+          }
+          else {
+            throw $e;
+          }
+        }
+      }
+    }
+  }
+}
+
+
+/**
+ * @} End of "addtogroup database".
+ */
diff --git a/core/drivers/mysql/src/Delete.php b/core/drivers/mysql/src/Delete.php
new file mode 100644
index 0000000..61694b0
--- /dev/null
+++ b/core/drivers/mysql/src/Delete.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Delete
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Query\Delete as QueryDelete;
+
+class Delete extends QueryDelete { }
diff --git a/core/drivers/mysql/src/Insert.php b/core/drivers/mysql/src/Insert.php
new file mode 100644
index 0000000..64e01bb
--- /dev/null
+++ b/core/drivers/mysql/src/Insert.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Insert
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Query\Insert as QueryInsert;
+
+class Insert extends QueryInsert {
+
+  public function execute() {
+    if (!$this->preExecute()) {
+      return NULL;
+    }
+
+    // If we're selecting from a SelectQuery, finish building the query and
+    // pass it back, as any remaining options are irrelevant.
+    if (empty($this->fromQuery)) {
+      $max_placeholder = 0;
+      $values = array();
+      foreach ($this->insertValues as $insert_values) {
+        foreach ($insert_values as $value) {
+          $values[':db_insert_placeholder_' . $max_placeholder++] = $value;
+        }
+      }
+    }
+    else {
+      $values = $this->fromQuery->getArguments();
+    }
+
+    $last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
+
+    // Re-initialize the values array so that we can re-use this query.
+    $this->insertValues = array();
+
+    return $last_insert_id;
+  }
+
+  public function __toString() {
+    // Create a sanitized comment string to prepend to the query.
+    $comments = $this->connection->makeComment($this->comments);
+
+    // Default fields are always placed first for consistency.
+    $insert_fields = array_merge($this->defaultFields, $this->insertFields);
+
+    // If we're selecting from a SelectQuery, finish building the query and
+    // pass it back, as any remaining options are irrelevant.
+    if (!empty($this->fromQuery)) {
+      $insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
+      return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
+    }
+
+    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
+
+    $max_placeholder = 0;
+    $values = array();
+    if (count($this->insertValues)) {
+      foreach ($this->insertValues as $insert_values) {
+        $placeholders = array();
+
+        // Default fields aren't really placeholders, but this is the most convenient
+        // way to handle them.
+        $placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
+
+        $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) . ')';
+      }
+    }
+    else {
+      // If there are no values, then this is a default-only query. We still need to handle that.
+      $placeholders = array_fill(0, count($this->defaultFields), 'default');
+      $values[] = '(' . implode(', ', $placeholders) . ')';
+    }
+
+    $query .= implode(', ', $values);
+
+    return $query;
+  }
+}
diff --git a/core/drivers/mysql/src/Install/Tasks.php b/core/drivers/mysql/src/Install/Tasks.php
new file mode 100644
index 0000000..21e2b22
--- /dev/null
+++ b/core/drivers/mysql/src/Install/Tasks.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Install\Tasks
+ */
+
+namespace Drupal\mysql\Install;
+
+use Drupal\Core\Database\Install\Tasks as InstallTasks;
+use Drupal\Core\Database\Database;
+use Drupal\mysql\Connection;
+use Drupal\Core\Database\DatabaseNotFoundException;
+
+/**
+ * Specifies installation tasks for MySQL and equivalent databases.
+ */
+class Tasks extends InstallTasks {
+  /**
+   * The PDO driver name for MySQL and equivalent databases.
+   *
+   * @var string
+   */
+  protected $pdoDriver = 'mysql';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function name() {
+    return t('MySQL, MariaDB, Percona Server, or equivalent');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function minimumVersion() {
+    return '5.1.21';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function connect() {
+    try {
+      // This doesn't actually test the connection.
+      db_set_active();
+      // Now actually do a check.
+      Database::getConnection();
+      $this->pass('Drupal can CONNECT to the database ok.');
+    }
+    catch (\Exception $e) {
+      // Attempt to create the database if it is not found.
+      if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
+        // Remove the database string from connection info.
+        $connection_info = Database::getConnectionInfo();
+        $database = $connection_info['default']['database'];
+        unset($connection_info['default']['database']);
+
+        // In order to change the Database::$databaseInfo array, need to remove
+        // the active connection, then re-add it with the new info.
+        Database::removeConnection('default');
+        Database::addConnectionInfo('default', 'default', $connection_info['default']);
+
+        try {
+          // Now, attempt the connection again; if it's successful, attempt to
+          // create the database.
+          Database::getConnection()->createDatabase($database);
+        }
+        catch (DatabaseNotFoundException $e) {
+          // Still no dice; probably a permission issue. Raise the error to the
+          // installer.
+          $this->fail(t('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
+        }
+      }
+      else {
+        // Database connection failed for some other reason than the database
+        // not existing.
+        $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist or does the database user have sufficient privileges to create the database?</li><li>Have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormOptions(array $database) {
+    $form = parent::getFormOptions($database);
+    if (empty($form['advanced_options']['port']['#default_value'])) {
+      $form['advanced_options']['port']['#default_value'] = '3306';
+    }
+
+    return $form;
+  }
+}
diff --git a/core/drivers/mysql/src/Merge.php b/core/drivers/mysql/src/Merge.php
new file mode 100644
index 0000000..9b24307
--- /dev/null
+++ b/core/drivers/mysql/src/Merge.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Merge
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Query\Merge as QueryMerge;
+
+class Merge extends QueryMerge { }
diff --git a/core/drivers/mysql/src/Schema.php b/core/drivers/mysql/src/Schema.php
new file mode 100644
index 0000000..68357ce
--- /dev/null
+++ b/core/drivers/mysql/src/Schema.php
@@ -0,0 +1,545 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Schema
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaObjectDoesNotExistException;
+use Drupal\Core\Database\Schema as DatabaseSchema;
+use Drupal\Component\Utility\Unicode;
+
+/**
+ * @addtogroup schemaapi
+ * @{
+ */
+
+class Schema extends DatabaseSchema {
+
+  /**
+   * Maximum length of a table comment in MySQL.
+   */
+  const COMMENT_MAX_TABLE = 60;
+
+  /**
+   * Maximum length of a column comment in MySQL.
+   */
+  const COMMENT_MAX_COLUMN = 255;
+
+  /**
+   * Get information about the table and database name from the prefix.
+   *
+   * @return
+   *   A keyed array with information about the database, table name and prefix.
+   */
+  protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
+    $info = array('prefix' => $this->connection->tablePrefix($table));
+    if ($add_prefix) {
+      $table = $info['prefix'] . $table;
+    }
+    if (($pos = strpos($table, '.')) !== FALSE) {
+      $info['database'] = substr($table, 0, $pos);
+      $info['table'] = substr($table, ++$pos);
+    }
+    else {
+      $db_info = Database::getConnectionInfo();
+      $info['database'] = $db_info[$this->connection->getTarget()]['database'];
+      $info['table'] = $table;
+    }
+    return $info;
+  }
+
+  /**
+   * Build a condition to match a table name against a standard information_schema.
+   *
+   * MySQL uses databases like schemas rather than catalogs so when we build
+   * a condition to query the information_schema.tables, we set the default
+   * database as the schema unless specified otherwise, and exclude table_catalog
+   * from the condition criteria.
+   */
+  protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
+    $table_info = $this->getPrefixInfo($table_name, $add_prefix);
+
+    $condition = new Condition('AND');
+    $condition->condition('table_schema', $table_info['database']);
+    $condition->condition('table_name', $table_info['table'], $operator);
+    return $condition;
+  }
+
+  /**
+   * 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.
+   */
+  protected function createTableSql($name, $table) {
+    $info = $this->connection->getConnectionOptions();
+
+    // Provide defaults if needed.
+    $table += array(
+      'mysql_engine' => 'InnoDB',
+      'mysql_character_set' => 'utf8',
+    );
+
+    $sql = "CREATE TABLE {" . $name . "} (\n";
+
+    // Add the SQL statement for each field.
+    foreach ($table['fields'] as $field_name => $field) {
+      $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
+    }
+
+    // Process keys & indexes.
+    $keys = $this->createKeysSql($table);
+    if (count($keys)) {
+      $sql .= implode(", \n", $keys) . ", \n";
+    }
+
+    // Remove the last comma and space.
+    $sql = substr($sql, 0, -3) . "\n) ";
+
+    $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
+    // By default, MySQL uses the default collation for new tables, which is
+    // 'utf8_general_ci' for utf8. If an alternate collation has been set, it
+    // needs to be explicitly specified.
+    // @see DatabaseConnection_mysql
+    if (!empty($info['collation'])) {
+      $sql .= ' COLLATE ' . $info['collation'];
+    }
+
+    // Add table comment.
+    if (!empty($table['description'])) {
+      $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
+    }
+
+    return array($sql);
+  }
+
+  /**
+   * 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.
+   */
+  protected function createFieldSql($name, $spec) {
+    $sql = "`" . $name . "` " . $spec['mysql_type'];
+
+    if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) {
+      if (isset($spec['length'])) {
+        $sql .= '(' . $spec['length'] . ')';
+      }
+      if (!empty($spec['binary'])) {
+        $sql .= ' BINARY';
+      }
+    }
+    elseif (isset($spec['precision']) && isset($spec['scale'])) {
+      $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
+    }
+
+    if (!empty($spec['unsigned'])) {
+      $sql .= ' unsigned';
+    }
+
+    if (isset($spec['not null'])) {
+      if ($spec['not null']) {
+        $sql .= ' NOT NULL';
+      }
+      else {
+        $sql .= ' NULL';
+      }
+    }
+
+    if (!empty($spec['auto_increment'])) {
+      $sql .= ' auto_increment';
+    }
+
+    // $spec['default'] can be NULL, so we explicitly check for the key here.
+    if (array_key_exists('default', $spec)) {
+      $sql .= ' DEFAULT ' . $this->escapeDefaultValue($spec['default']);
+    }
+
+    if (empty($spec['not null']) && !isset($spec['default'])) {
+      $sql .= ' DEFAULT NULL';
+    }
+
+    // Add column comment.
+    if (!empty($spec['description'])) {
+      $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
+    }
+
+    return $sql;
+  }
+
+  /**
+   * Set database-engine specific properties for a field.
+   *
+   * @param $field
+   *   A field description array, as specified in the schema documentation.
+   */
+  protected function processField($field) {
+
+    if (!isset($field['size'])) {
+      $field['size'] = 'normal';
+    }
+
+    // Set the correct database-engine specific datatype.
+    // In case one is already provided, force it to uppercase.
+    if (isset($field['mysql_type'])) {
+      $field['mysql_type'] = drupal_strtoupper($field['mysql_type']);
+    }
+    else {
+      $map = $this->getFieldTypeMap();
+      $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
+    }
+
+    if (isset($field['type']) && $field['type'] == 'serial') {
+      $field['auto_increment'] = TRUE;
+    }
+
+    return $field;
+  }
+
+  public function getFieldTypeMap() {
+    // 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 does not use drupal_static as its value never changes.
+    static $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',
+    );
+    return $map;
+  }
+
+  protected function createKeysSql($spec) {
+    $keys = array();
+
+    if (!empty($spec['primary key'])) {
+      $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')';
+    }
+    if (!empty($spec['unique keys'])) {
+      foreach ($spec['unique keys'] as $key => $fields) {
+        $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')';
+      }
+    }
+    if (!empty($spec['indexes'])) {
+      foreach ($spec['indexes'] as $index => $fields) {
+        $keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')';
+      }
+    }
+
+    return $keys;
+  }
+
+  protected function createKeySql($fields) {
+    $return = array();
+    foreach ($fields as $field) {
+      if (is_array($field)) {
+        $return[] = '`' . $field[0] . '`(' . $field[1] . ')';
+      }
+      else {
+        $return[] = '`' . $field . '`';
+      }
+    }
+    return implode(', ', $return);
+  }
+
+  protected function createKeysSqlHelper($fields) {
+    $return = array();
+    foreach ($fields as $field) {
+      if (is_array($field)) {
+        $return[] = '`' . $field[0] . '`(' . $field[1] . ')';
+      }
+      else {
+        $return[] = '`' . $field . '`';
+      }
+    }
+    return implode(', ', $return);
+  }
+
+  public function renameTable($table, $new_name) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
+    }
+    if ($this->tableExists($new_name)) {
+      throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
+    }
+
+    $info = $this->getPrefixInfo($new_name);
+    return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
+  }
+
+  public function dropTable($table) {
+    if (!$this->tableExists($table)) {
+      return FALSE;
+    }
+
+    $this->connection->query('DROP TABLE {' . $table . '}');
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function copyTable($source, $destination) {
+    if (!$this->tableExists($source)) {
+      throw new SchemaObjectDoesNotExistException(String::format("Cannot copy @source to @destination: table @source doesn't exist.", array('@source' => $source, '@destination' => $destination)));
+    }
+    if ($this->tableExists($destination)) {
+      throw new SchemaObjectExistsException(String::format("Cannot copy @source to @destination: table @destination already exists.", array('@source' => $source, '@destination' => $destination)));
+    }
+
+    $info = $this->getPrefixInfo($destination);
+    return $this->connection->query('CREATE TABLE `' . $info['table'] . '` LIKE {' . $source . '}');
+  }
+
+  public function addField($table, $field, $spec, $keys_new = array()) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
+    }
+    if ($this->fieldExists($table, $field)) {
+      throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
+    }
+
+    $fixnull = FALSE;
+    if (!empty($spec['not null']) && !isset($spec['default'])) {
+      $fixnull = TRUE;
+      $spec['not null'] = FALSE;
+    }
+    $query = 'ALTER TABLE {' . $table . '} ADD ';
+    $query .= $this->createFieldSql($field, $this->processField($spec));
+    if ($keys_sql = $this->createKeysSql($keys_new)) {
+      $query .= ', ADD ' . implode(', ADD ', $keys_sql);
+    }
+    $this->connection->query($query);
+    if (isset($spec['initial'])) {
+      $this->connection->update($table)
+        ->fields(array($field => $spec['initial']))
+        ->execute();
+    }
+    if ($fixnull) {
+      $spec['not null'] = TRUE;
+      $this->changeField($table, $field, $field, $spec);
+    }
+  }
+
+  public function dropField($table, $field) {
+    if (!$this->fieldExists($table, $field)) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
+    return TRUE;
+  }
+
+  public function fieldSetDefault($table, $field, $default) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $this->escapeDefaultValue($default));
+  }
+
+  public function fieldSetNoDefault($table, $field) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
+  }
+
+  public function indexExists($table, $name) {
+    // Returns one row for each column in the index. Result is string or FALSE.
+    // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
+    $row = $this->connection->query('SHOW INDEX FROM {' . $table . '} WHERE key_name = ' . $this->connection->quote($name))->fetchAssoc();
+    return isset($row['Key_name']);
+  }
+
+  public function addPrimaryKey($table, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
+    }
+    if ($this->indexExists($table, 'PRIMARY')) {
+      throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
+  }
+
+  public function dropPrimaryKey($table) {
+    if (!$this->indexExists($table, 'PRIMARY')) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
+    return TRUE;
+  }
+
+  public function addUniqueKey($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->indexExists($table, $name)) {
+      throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
+  }
+
+  public function dropUniqueKey($table, $name) {
+    if (!$this->indexExists($table, $name)) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`');
+    return TRUE;
+  }
+
+  public function addIndex($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->indexExists($table, $name)) {
+      throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
+  }
+
+  public function dropIndex($table, $name) {
+    if (!$this->indexExists($table, $name)) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
+    return TRUE;
+  }
+
+  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
+    }
+    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
+      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
+    }
+
+    $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
+    if ($keys_sql = $this->createKeysSql($keys_new)) {
+      $sql .= ', ADD ' . implode(', ADD ', $keys_sql);
+    }
+    $this->connection->query($sql);
+  }
+
+  public function prepareComment($comment, $length = NULL) {
+    // Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125
+    $comment = str_replace("'", '’', $comment);
+
+    // Truncate comment to maximum comment length.
+    if (isset($length)) {
+      // Add table prefixes before truncating.
+      $comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
+    }
+
+    return $this->connection->quote($comment);
+  }
+
+  /**
+   * Retrieve a table or column comment.
+   */
+  public function getComment($table, $column = NULL) {
+    $condition = $this->buildTableNameCondition($table);
+    if (isset($column)) {
+      $condition->condition('column_name', $column);
+      $condition->compile($this->connection, $this);
+      // Don't use {} around information_schema.columns table.
+      return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
+    }
+    $condition->compile($this->connection, $this);
+    // Don't use {} around information_schema.tables table.
+    $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
+    // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
+    return preg_replace('/; InnoDB free:.*$/', '', $comment);
+  }
+
+  public function tableExists($table) {
+    // The information_schema table is very slow to query under MySQL 5.0.
+    // Instead, we try to select from the table in question.  If it fails,
+    // the most likely reason is that it does not exist. That is dramatically
+    // faster than using information_schema.
+    // @link http://bugs.mysql.com/bug.php?id=19588
+    // @todo This override should be removed once we require a version of MySQL
+    //   that has that bug fixed.
+    try {
+      $this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1);
+      return TRUE;
+    }
+    catch (\Exception $e) {
+      return FALSE;
+    }
+  }
+
+  public function fieldExists($table, $column) {
+    // The information_schema table is very slow to query under MySQL 5.0.
+    // Instead, we try to select from the table and field in question. If it
+    // fails, the most likely reason is that it does not exist. That is
+    // dramatically faster than using information_schema.
+    // @link http://bugs.mysql.com/bug.php?id=19588
+    // @todo This override should be removed once we require a version of MySQL
+    //   that has that bug fixed.
+    try {
+      $this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1);
+      return TRUE;
+    }
+    catch (\Exception $e) {
+      return FALSE;
+    }
+  }
+
+}
+
+/**
+ * @} End of "addtogroup schemaapi".
+ */
diff --git a/core/drivers/mysql/src/Select.php b/core/drivers/mysql/src/Select.php
new file mode 100644
index 0000000..3a462ca
--- /dev/null
+++ b/core/drivers/mysql/src/Select.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Select
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Query\Select as QuerySelect;
+
+class Select extends QuerySelect { }
diff --git a/core/drivers/mysql/src/Transaction.php b/core/drivers/mysql/src/Transaction.php
new file mode 100644
index 0000000..9d2d87e
--- /dev/null
+++ b/core/drivers/mysql/src/Transaction.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Transaction
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Transaction as DatabaseTransaction;
+
+class Transaction extends DatabaseTransaction { }
diff --git a/core/drivers/mysql/src/Truncate.php b/core/drivers/mysql/src/Truncate.php
new file mode 100644
index 0000000..8cb513f
--- /dev/null
+++ b/core/drivers/mysql/src/Truncate.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Truncate
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Query\Truncate as QueryTruncate;
+
+class Truncate extends QueryTruncate {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $this->connection->addSavepoint();
+    try {
+      $result = parent::execute();
+    }
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
+    }
+    $this->connection->releaseSavepoint();
+
+    return $result;
+  }
+}
diff --git a/core/drivers/mysql/src/Update.php b/core/drivers/mysql/src/Update.php
new file mode 100644
index 0000000..48bfeaf
--- /dev/null
+++ b/core/drivers/mysql/src/Update.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\mysql\Update
+ */
+
+namespace Drupal\mysql;
+
+use Drupal\Core\Database\Query\Update as QueryUpdate;
+
+class Update extends QueryUpdate { }
diff --git a/core/drivers/pgsql/pgsql.info.yml b/core/drivers/pgsql/pgsql.info.yml
new file mode 100644
index 0000000..1e994c4
--- /dev/null
+++ b/core/drivers/pgsql/pgsql.info.yml
@@ -0,0 +1,3 @@
+type: driver
+core: 8.x
+name: PostgreSQL
diff --git a/core/drivers/pgsql/src/Connection.php b/core/drivers/pgsql/src/Connection.php
new file mode 100644
index 0000000..107c5c2
--- /dev/null
+++ b/core/drivers/pgsql/src/Connection.php
@@ -0,0 +1,330 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Connection
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Connection as DatabaseConnection;
+use Drupal\Core\Database\DatabaseNotFoundException;
+use Drupal\Core\Database\StatementInterface;
+use Drupal\Core\Database\IntegrityConstraintViolationException;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+
+/**
+ * @addtogroup database
+ * @{
+ */
+
+class Connection extends DatabaseConnection {
+
+  /**
+   * The name by which to obtain a lock for retrieve the next insert id.
+   */
+  const POSTGRESQL_NEXTID_LOCK = 1000;
+
+  /**
+   * Error code for "Unknown database" error.
+   */
+  const DATABASE_NOT_FOUND = 7;
+
+  /**
+   * Constructs a connection object.
+   */
+  public function __construct(\PDO $connection, array $connection_options) {
+    parent::__construct($connection, $connection_options);
+
+    // This driver defaults to transaction support, except if explicitly passed FALSE.
+    $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
+
+    // Transactional DDL is always available in PostgreSQL,
+    // but we'll only enable it if standard transactions are.
+    $this->transactionalDDLSupport = $this->transactionSupport;
+
+    $this->connectionOptions = $connection_options;
+
+    // Force PostgreSQL to use the UTF-8 character set by default.
+    $this->connection->exec("SET NAMES 'UTF8'");
+
+    // Execute PostgreSQL init_commands.
+    if (isset($connection_options['init_commands'])) {
+      $this->connection->exec(implode('; ', $connection_options['init_commands']));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function open(array &$connection_options = array()) {
+    // Default to TCP connection on port 5432.
+    if (empty($connection_options['port'])) {
+      $connection_options['port'] = 5432;
+    }
+
+    // PostgreSQL in trust mode doesn't require a password to be supplied.
+    if (empty($connection_options['password'])) {
+      $connection_options['password'] = NULL;
+    }
+    // If the password contains a backslash it is treated as an escape character
+    // http://bugs.php.net/bug.php?id=53217
+    // so backslashes in the password need to be doubled up.
+    // The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords
+    // will break on this doubling up when the bug is fixed, so check the version
+    //elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') {
+    else {
+      $connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']);
+    }
+
+    $connection_options['database'] = (!empty($connection_options['database']) ? $connection_options['database'] : 'template1');
+    $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
+
+    // Allow PDO options to be overridden.
+    $connection_options += array(
+      'pdo' => array(),
+    );
+    $connection_options['pdo'] += array(
+      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+      // Prepared statements are most effective for performance when queries
+      // are recycled (used several times). However, if they are not re-used,
+      // prepared statements become inefficient. Since most of Drupal's
+      // prepared queries are not re-used, it should be faster to emulate
+      // the preparation than to actually ready statements for re-use. If in
+      // doubt, reset to FALSE and measure performance.
+      \PDO::ATTR_EMULATE_PREPARES => TRUE,
+      // Convert numeric values to strings when fetching.
+      \PDO::ATTR_STRINGIFY_FETCHES => TRUE,
+    );
+    $pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
+
+    return $pdo;
+  }
+
+  public function query($query, array $args = array(), $options = array()) {
+
+    $options += $this->defaultOptions();
+
+    // The PDO PostgreSQL driver has a bug which
+    // doesn't type cast booleans correctly when
+    // parameters are bound using associative
+    // arrays.
+    // See http://bugs.php.net/bug.php?id=48383
+    foreach ($args as &$value) {
+      if (is_bool($value)) {
+        $value = (int) $value;
+      }
+    }
+
+    try {
+      if ($query instanceof StatementInterface) {
+        $stmt = $query;
+        $stmt->execute(NULL, $options);
+      }
+      else {
+        $this->expandArguments($query, $args);
+        $stmt = $this->prepareQuery($query);
+        $stmt->execute($args, $options);
+      }
+
+      switch ($options['return']) {
+        case Database::RETURN_STATEMENT:
+          return $stmt;
+        case Database::RETURN_AFFECTED:
+          $stmt->allowRowCount = TRUE;
+          return $stmt->rowCount();
+        case Database::RETURN_INSERT_ID:
+          return $this->connection->lastInsertId($options['sequence_name']);
+        case Database::RETURN_NULL:
+          return;
+        default:
+          throw new \PDOException('Invalid return directive: ' . $options['return']);
+      }
+    }
+    catch (\PDOException $e) {
+      if ($options['throw_exception']) {
+        // Match all SQLSTATE 23xxx errors.
+        if (substr($e->getCode(), -6, -3) == '23') {
+          $e = new IntegrityConstraintViolationException($e->getMessage(), $e->getCode(), $e);
+        }
+        else {
+          $e = new DatabaseExceptionWrapper($e->getMessage(), 0, $e);
+        }
+        // Add additional debug information.
+        if ($query instanceof StatementInterface) {
+          $e->query_string = $stmt->getQueryString();
+        }
+        else {
+          $e->query_string = $query;
+        }
+        $e->args = $args;
+        throw $e;
+      }
+      return NULL;
+    }
+  }
+
+  public function prepareQuery($query) {
+    // mapConditionOperator converts LIKE operations to ILIKE for consistency
+    // with MySQL. However, Postgres does not support ILIKE on bytea (blobs)
+    // fields.
+    // To make the ILIKE operator work, we type-cast bytea fields into text.
+    // @todo This workaround only affects bytea fields, but the involved field
+    //   types involved in the query are unknown, so there is no way to
+    //   conditionally execute this for affected queries only.
+    return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query));
+  }
+
+  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
+    return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options);
+  }
+
+  public function queryTemporary($query, array $args = array(), array $options = array()) {
+    $tablename = $this->generateTemporaryTableName();
+    $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options);
+    return $tablename;
+  }
+
+  public function driver() {
+    return 'pgsql';
+  }
+
+  public function databaseType() {
+    return 'pgsql';
+  }
+
+  /**
+   * Overrides \Drupal\Core\Database\Connection::createDatabase().
+   *
+   * @param string $database
+   *   The name of the database to create.
+   *
+   * @throws \Drupal\Core\Database\DatabaseNotFoundException
+   */
+  public function createDatabase($database) {
+    // Escape the database name.
+    $database = Database::getConnection()->escapeDatabase($database);
+
+    // If the PECL intl extension is installed, use it to determine the proper
+    // locale.  Otherwise, fall back to en_US.
+    if (class_exists('Locale')) {
+      $locale = \Locale::getDefault();
+    }
+    else {
+      $locale = 'en_US';
+    }
+
+    try {
+      // Create the database and set it as active.
+      $this->connection->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='utf8' LC_CTYPE='$locale.utf8' LC_COLLATE='$locale.utf8'");
+    }
+    catch (\Exception $e) {
+      throw new DatabaseNotFoundException($e->getMessage());
+    }
+  }
+
+  public function mapConditionOperator($operator) {
+    static $specials = array(
+      // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
+      // statements, we need to use ILIKE instead.
+      'LIKE' => array('operator' => 'ILIKE'),
+      'NOT LIKE' => array('operator' => 'NOT ILIKE'),
+      'REGEXP' => array('operator' => '~*'),
+    );
+    return isset($specials[$operator]) ? $specials[$operator] : NULL;
+  }
+
+  /**
+   * Retrieve a the next id in a sequence.
+   *
+   * PostgreSQL has built in sequences. We'll use these instead of inserting
+   * and updating a sequences table.
+   */
+  public function nextId($existing = 0) {
+
+    // Retrieve the name of the sequence. This information cannot be cached
+    // because the prefix may change, for example, like it does in simpletests.
+    $sequence_name = $this->makeSequenceName('sequences', 'value');
+
+    // When PostgreSQL gets a value too small then it will lock the table,
+    // retry the INSERT and if it's still too small then alter the sequence.
+    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
+    if ($id > $existing) {
+      return $id;
+    }
+
+    // PostgreSQL advisory locks are simply locks to be used by an
+    // application such as Drupal. This will prevent other Drupal processes
+    // from altering the sequence while we are.
+    $this->query("SELECT pg_advisory_lock(" . self::POSTGRESQL_NEXTID_LOCK . ")");
+
+    // While waiting to obtain the lock, the sequence may have been altered
+    // so lets try again to obtain an adequate value.
+    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
+    if ($id > $existing) {
+      $this->query("SELECT pg_advisory_unlock(" . self::POSTGRESQL_NEXTID_LOCK . ")");
+      return $id;
+    }
+
+    // Reset the sequence to a higher value than the existing id.
+    $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1));
+
+    // Retrieve the next id. We know this will be as high as we want it.
+    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
+
+    $this->query("SELECT pg_advisory_unlock(" . self::POSTGRESQL_NEXTID_LOCK . ")");
+
+    return $id;
+  }
+
+  /**
+   * Add a new savepoint with an unique name.
+   *
+   * The main use for this method is to mimic InnoDB functionality, which
+   * provides an inherent savepoint before any query in a transaction.
+   *
+   * @param $savepoint_name
+   *   A string representing the savepoint name. By default,
+   *   "mimic_implicit_commit" is used.
+   *
+   * @see Drupal\Core\Database\Connection::pushTransaction().
+   */
+  public function addSavepoint($savepoint_name = 'mimic_implicit_commit') {
+    if ($this->inTransaction()) {
+      $this->pushTransaction($savepoint_name);
+    }
+  }
+
+  /**
+   * Release a savepoint by name.
+   *
+   * @param $savepoint_name
+   *   A string representing the savepoint name. By default,
+   *   "mimic_implicit_commit" is used.
+   *
+   * @see Drupal\Core\Database\Connection::popTransaction().
+   */
+  public function releaseSavepoint($savepoint_name = 'mimic_implicit_commit') {
+    if (isset($this->transactionLayers[$savepoint_name])) {
+      $this->popTransaction($savepoint_name);
+    }
+  }
+
+  /**
+   * Rollback a savepoint by name if it exists.
+   *
+   * @param $savepoint_name
+   *   A string representing the savepoint name. By default,
+   *   "mimic_implicit_commit" is used.
+   */
+  public function rollbackSavepoint($savepoint_name = 'mimic_implicit_commit') {
+    if (isset($this->transactionLayers[$savepoint_name])) {
+      $this->rollback($savepoint_name);
+    }
+  }
+}
+
+/**
+ * @} End of "addtogroup database".
+ */
diff --git a/core/drivers/pgsql/src/Delete.php b/core/drivers/pgsql/src/Delete.php
new file mode 100644
index 0000000..d3765c4
--- /dev/null
+++ b/core/drivers/pgsql/src/Delete.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Delete
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Query\Delete as QueryDelete;
+
+class Delete extends QueryDelete {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $this->connection->addSavepoint();
+    try {
+      $result = parent::execute();
+    }
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
+    }
+    $this->connection->releaseSavepoint();
+
+    return $result;
+  }
+
+}
diff --git a/core/drivers/pgsql/src/Insert.php b/core/drivers/pgsql/src/Insert.php
new file mode 100644
index 0000000..d7f6dc3
--- /dev/null
+++ b/core/drivers/pgsql/src/Insert.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Insert
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Query\Insert as QueryInsert;
+
+/**
+ * @ingroup database
+ * @{
+ */
+
+class Insert extends QueryInsert {
+
+  public function execute() {
+    if (!$this->preExecute()) {
+      return NULL;
+    }
+
+    $stmt = $this->connection->prepareQuery((string) $this);
+
+    // Fetch the list of blobs and sequences used on that table.
+    $table_information = $this->connection->schema()->queryTableInformation($this->table);
+
+    $max_placeholder = 0;
+    $blobs = array();
+    $blob_count = 0;
+    foreach ($this->insertValues as $insert_values) {
+      foreach ($this->insertFields as $idx => $field) {
+        if (isset($table_information->blob_fields[$field])) {
+          $blobs[$blob_count] = fopen('php://memory', 'a');
+          fwrite($blobs[$blob_count], $insert_values[$idx]);
+          rewind($blobs[$blob_count]);
+
+          $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], \PDO::PARAM_LOB);
+
+          // Pre-increment is faster in PHP than increment.
+          ++$blob_count;
+        }
+        else {
+          $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
+        }
+      }
+      // Check if values for a serial field has been passed.
+      if (!empty($table_information->serial_fields)) {
+        foreach ($table_information->serial_fields as $index => $serial_field) {
+          $serial_key = array_search($serial_field, $this->insertFields);
+          if ($serial_key !== FALSE) {
+            $serial_value = $insert_values[$serial_key];
+
+            // Force $last_insert_id to the specified value. This is only done
+            // if $index is 0.
+            if ($index == 0) {
+              $last_insert_id = $serial_value;
+            }
+            // Sequences must be greater than or equal to 1.
+            if ($serial_value === NULL || !$serial_value) {
+              $serial_value = 1;
+            }
+            // Set the sequence to the bigger value of either the passed
+            // value or the max value of the column. It can happen that another
+            // thread calls nextval() which could lead to a serial number being
+            // used twice. However, trying to insert a value into a serial
+            // column should only be done in very rare cases and is not thread
+            // safe by definition.
+            $this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value));
+          }
+        }
+      }
+    }
+    if (!empty($this->fromQuery)) {
+      // bindParam stores only a reference to the variable that is followed when
+      // the statement is executed. We pass $arguments[$key] instead of $value
+      // because the second argument to bindParam is passed by reference and
+      // the foreach statement assigns the element to the existing reference.
+      $arguments = $this->fromQuery->getArguments();
+      foreach ($arguments as $key => $value) {
+        $stmt->bindParam($key, $arguments[$key]);
+      }
+    }
+
+    // PostgreSQL requires the table name to be specified explicitly
+    // when requesting the last insert ID, so we pass that in via
+    // the options array.
+    $options = $this->queryOptions;
+
+    if (!empty($table_information->sequences)) {
+      $options['sequence_name'] = $table_information->sequences[0];
+    }
+    // If there are no sequences then we can't get a last insert id.
+    elseif ($options['return'] == Database::RETURN_INSERT_ID) {
+      $options['return'] = Database::RETURN_NULL;
+    }
+    // Only use the returned last_insert_id if it is not already set.
+    if (!empty($last_insert_id)) {
+      $this->connection->query($stmt, array(), $options);
+    }
+    else {
+      $last_insert_id = $this->connection->query($stmt, array(), $options);
+    }
+
+    // Re-initialize the values array so that we can re-use this query.
+    $this->insertValues = array();
+
+    return $last_insert_id;
+  }
+
+  public function __toString() {
+    // Create a sanitized comment string to prepend to the query.
+    $comments = $this->connection->makeComment($this->comments);
+
+    // Default fields are always placed first for consistency.
+    $insert_fields = array_merge($this->defaultFields, $this->insertFields);
+
+    // If we're selecting from a SelectQuery, finish building the query and
+    // pass it back, as any remaining options are irrelevant.
+    if (!empty($this->fromQuery)) {
+      $insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
+      return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
+    }
+
+    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
+
+    $max_placeholder = 0;
+    $values = array();
+      if (count($this->insertValues)) {
+      foreach ($this->insertValues as $insert_values) {
+        $placeholders = array();
+
+        // Default fields aren't really placeholders, but this is the most convenient
+        // way to handle them.
+        $placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
+
+        $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) . ')';
+      }
+    }
+    else {
+      // If there are no values, then this is a default-only query. We still need to handle that.
+      $placeholders = array_fill(0, count($this->defaultFields), 'default');
+      $values[] = '(' . implode(', ', $placeholders) . ')';
+    }
+
+    $query .= implode(', ', $values);
+
+    return $query;
+  }
+}
diff --git a/core/drivers/pgsql/src/Install/Tasks.php b/core/drivers/pgsql/src/Install/Tasks.php
new file mode 100644
index 0000000..e7dd55f
--- /dev/null
+++ b/core/drivers/pgsql/src/Install/Tasks.php
@@ -0,0 +1,258 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Install\Tasks
+ */
+
+namespace Drupal\pgsql\Install;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Install\Tasks as InstallTasks;
+use Drupal\pgsql\Connection;
+use Drupal\Core\Database\DatabaseNotFoundException;
+
+/**
+ * Specifies installation tasks for PostgreSQL databases.
+ */
+class Tasks extends InstallTasks {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $pdoDriver = 'pgsql';
+
+  /**
+   * Constructs a \Drupal\pgsql\Install\Tasks object.
+   */
+  public function __construct() {
+    $this->tasks[] = array(
+      'function' => 'checkEncoding',
+      'arguments' => array(),
+    );
+    $this->tasks[] = array(
+      'function' => 'checkBinaryOutput',
+      'arguments' => array(),
+    );
+    $this->tasks[] = array(
+      'function' => 'initializeDatabase',
+      'arguments' => array(),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function name() {
+    return t('PostgreSQL');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function minimumVersion() {
+    return '8.3';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function connect() {
+    try {
+      // This doesn't actually test the connection.
+      db_set_active();
+      // Now actually do a check.
+      Database::getConnection();
+      $this->pass('Drupal can CONNECT to the database ok.');
+    }
+    catch (\Exception $e) {
+    // Attempt to create the database if it is not found.
+      if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
+        // Remove the database string from connection info.
+        $connection_info = Database::getConnectionInfo();
+        $database = $connection_info['default']['database'];
+        unset($connection_info['default']['database']);
+
+        // In order to change the Database::$databaseInfo array, need to remove
+        // the active connection, then re-add it with the new info.
+        Database::removeConnection('default');
+        Database::addConnectionInfo('default', 'default', $connection_info['default']);
+
+        try {
+          // Now, attempt the connection again; if it's successful, attempt to
+          // create the database.
+          Database::getConnection()->createDatabase($database);
+          Database::closeConnection();
+
+          // Now, restore the database config.
+          Database::removeConnection('default');
+          $connection_info['default']['database'] = $database;
+          Database::addConnectionInfo('default', 'default', $connection_info['default']);
+
+          // Check the database connection.
+          Database::getConnection();
+          $this->pass('Drupal can CONNECT to the database ok.');
+        }
+        catch (DatabaseNotFoundException $e) {
+          // Still no dice; probably a permission issue. Raise the error to the
+          // installer.
+          $this->fail(t('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
+        }
+      }
+      else {
+        // Database connection failed for some other reason than the database
+        // not existing.
+        $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * Check encoding is UTF8.
+   */
+  protected function checkEncoding() {
+    try {
+      if (db_query('SHOW server_encoding')->fetchField() == 'UTF8') {
+        $this->pass(t('Database is encoded in UTF-8'));
+      }
+      else {
+        $this->fail(t('The %driver database must use %encoding encoding to work with Drupal. Recreate the database with %encoding encoding. See !link for more details.', array(
+          '%encoding' => 'UTF8',
+          '%driver' => $this->name(),
+          '!link' => '<a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a>'
+        )));
+      }
+    }
+    catch (\Exception $e) {
+      $this->fail(t('Drupal could not determine the encoding of the database was set to UTF-8'));
+    }
+  }
+
+  /**
+   * Check Binary Output.
+   *
+   * Unserializing does not work on Postgresql 9 when bytea_output is 'hex'.
+   */
+  function checkBinaryOutput() {
+    // PostgreSQL < 9 doesn't support bytea_output, so verify we are running
+    // at least PostgreSQL 9.
+    $database_connection = Database::getConnection();
+    if (version_compare($database_connection->version(), '9') >= 0) {
+      if (!$this->checkBinaryOutputSuccess()) {
+        // First try to alter the database. If it fails, raise an error telling
+        // the user to do it themselves.
+        $connection_options = $database_connection->getConnectionOptions();
+        // It is safe to include the database name directly here, because this
+        // code is only called when a connection to the database is already
+        // established, thus the database name is guaranteed to be a correct
+        // value.
+        $query = "ALTER DATABASE \"" . $connection_options['database'] . "\" SET bytea_output = 'escape';";
+        try {
+          db_query($query);
+        }
+        catch (\Exception $e) {
+          // Ignore possible errors when the user doesn't have the necessary
+          // privileges to ALTER the database.
+        }
+
+        // Close the database connection so that the configuration parameter
+        // is applied to the current connection.
+        db_close();
+
+        // Recheck, if it fails, finally just rely on the end user to do the
+        // right thing.
+        if (!$this->checkBinaryOutputSuccess()) {
+          $replacements = array(
+            '%setting' => 'bytea_output',
+            '%current_value' => 'hex',
+            '%needed_value' => 'escape',
+            '!query' => "<code>" . $query . "</code>",
+          );
+          $this->fail(t("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: !query", $replacements));
+        }
+      }
+    }
+  }
+
+  /**
+   * Verify that a binary data roundtrip returns the original string.
+   */
+  protected function checkBinaryOutputSuccess() {
+    $bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
+    return ($bytea_output == 'encoding');
+  }
+
+  /**
+   * Make PostgreSQL Drupal friendly.
+   */
+  function initializeDatabase() {
+    // We create some functions using global names instead of prefixing them
+    // like we do with table names. This is so that we don't double up if more
+    // than one instance of Drupal is running on a single database. We therefore
+    // avoid trying to create them again in that case.
+
+    try {
+      // Create functions.
+      db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
+        \'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
+        LANGUAGE \'sql\''
+      );
+      db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
+        \'SELECT greatest($1, greatest($2, $3));\'
+        LANGUAGE \'sql\''
+      );
+      // Don't use {} around pg_proc table.
+      if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
+        db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
+          \'SELECT random();\'
+          LANGUAGE \'sql\''
+        );
+      }
+
+      db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
+        \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
+        LANGUAGE \'sql\''
+      );
+
+      // Using || to concatenate in Drupal is not recommended because there are
+      // database drivers for Drupal that do not support the syntax, however
+      // they do support CONCAT(item1, item2) which we can replicate in
+      // PostgreSQL. PostgreSQL requires the function to be defined for each
+      // different argument variation the function can handle.
+      db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS
+        \'SELECT CAST($1 AS text) || CAST($2 AS text);\'
+        LANGUAGE \'sql\'
+      ');
+      db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS
+        \'SELECT $1 || CAST($2 AS text);\'
+        LANGUAGE \'sql\'
+      ');
+      db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS
+        \'SELECT CAST($1 AS text) || $2;\'
+        LANGUAGE \'sql\'
+      ');
+      db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
+        \'SELECT $1 || $2;\'
+        LANGUAGE \'sql\'
+      ');
+
+      $this->pass(t('PostgreSQL has initialized itself.'));
+    }
+    catch (\Exception $e) {
+      $this->fail(t('Drupal could not be correctly setup with the existing database. Revise any errors.'));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormOptions(array $database) {
+    $form = parent::getFormOptions($database);
+    if (empty($form['advanced_options']['port']['#default_value'])) {
+      $form['advanced_options']['port']['#default_value'] = '5432';
+    }
+    return $form;
+  }
+}
diff --git a/core/drivers/pgsql/src/Merge.php b/core/drivers/pgsql/src/Merge.php
new file mode 100644
index 0000000..20f8d8f
--- /dev/null
+++ b/core/drivers/pgsql/src/Merge.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Merge
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Query\Merge as QueryMerge;
+
+class Merge extends QueryMerge { }
diff --git a/core/drivers/pgsql/src/Schema.php b/core/drivers/pgsql/src/Schema.php
new file mode 100644
index 0000000..dc96e82
--- /dev/null
+++ b/core/drivers/pgsql/src/Schema.php
@@ -0,0 +1,750 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Schema
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Query\Condition;
+use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaObjectDoesNotExistException;
+use Drupal\Core\Database\Schema as DatabaseSchema;
+
+/**
+ * @addtogroup schemaapi
+ * @{
+ */
+
+class Schema extends DatabaseSchema {
+
+  /**
+   * A cache of information about blob columns and sequences of tables.
+   *
+   * This is collected by DatabaseConnection_pgsql->queryTableInformation(),
+   * by introspecting the database.
+   *
+   * @see DatabaseConnection_pgsql->queryTableInformation()
+   * @var array
+   */
+  protected $tableInformation = array();
+
+  /**
+   * The maximum allowed length for index, primary key and constraint names.
+   *
+   * Value will usually be set to a 63 chars limit but PostgreSQL allows
+   * to higher this value before compiling, so we need to check for that.
+   *
+   * @var integer
+   */
+  protected $maxIdentifierLength;
+
+  /**
+   * Make sure to limit identifiers according to PostgreSQL compiled in length.
+   *
+   * PostgreSQL allows in standard configuration no longer identifiers than 63
+   * chars for table/relation names, indexes, primary keys, and constraints. So
+   * we map all identifiers that are too long to drupal_base64hash_tag, where
+   * tag is one of:
+   *   - idx for indexes
+   *   - key for constraints
+   *   - pkey for primary keys
+   *
+   * @param $identifiers
+   *   The arguments to build the identifier string
+   * @return
+   *   The index/constraint/pkey identifier
+   */
+  protected function ensureIdentifiersLength($identifier) {
+    $args = func_get_args();
+    $info = $this->getPrefixInfo($identifier);
+    $args[0] = $info['table'];
+    $identifierName = implode('__', $args);
+
+    // Retrieve the max identifier length which is usually 63 characters
+    // but can be altered before PostgreSQL is compiled so we need to check.
+    $this->maxIdentifierLength = $this->connection->query("SHOW max_identifier_length")->fetchField();
+
+    if (strlen($identifierName) > $this->maxIdentifierLength) {
+      $saveIdentifier = 'drupal_' . $this->hashBase64($identifierName) . '_' . $args[2];
+    }
+    else {
+      $saveIdentifier = $identifierName;
+    }
+    return $saveIdentifier;
+  }
+
+  /**
+   * Fetch the list of blobs and sequences used on a table.
+   *
+   * We introspect the database to collect the information required by insert
+   * and update queries.
+   *
+   * @param $table_name
+   *   The non-prefixed name of the table.
+   * @return
+   *   An object with two member variables:
+   *     - 'blob_fields' that lists all the blob fields in the table.
+   *     - 'sequences' that lists the sequences used in that table.
+   */
+  public function queryTableInformation($table) {
+    // Generate a key to reference this table's information on.
+    $key = $this->connection->prefixTables('{' . $table . '}');
+    if (!strpos($key, '.')) {
+      $key = 'public.' . $key;
+    }
+
+    if (!isset($this->tableInformation[$key])) {
+      // Split the key into schema and table for querying.
+      list($schema, $table_name) = explode('.', $key);
+      $table_information = (object) array(
+        'blob_fields' => array(),
+        'sequences' => array(),
+      );
+      // Don't use {} around information_schema.columns table.
+      $this->connection->addSavepoint();
+
+      try {
+        $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
+          ':schema' => $schema,
+          ':table' => $table_name,
+          ':default' => '%nextval%',
+        ));
+      }
+      catch (\Exception $e) {
+        $this->connection->rollbackSavepoint();
+        throw $e;
+      }
+      $this->connection->releaseSavepoint();
+
+      foreach ($result as $column) {
+        if ($column->data_type == 'bytea') {
+          $table_information->blob_fields[$column->column_name] = TRUE;
+        }
+        elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
+          // We must know of any sequences in the table structure to help us
+          // return the last insert id. If there is more than 1 sequences the
+          // first one (index 0 of the sequences array) will be used.
+          $table_information->sequences[] = $matches[1];
+          $table_information->serial_fields[] = $column->column_name;
+        }
+      }
+      $this->tableInformation[$key] = $table_information;
+    }
+    return $this->tableInformation[$key];
+  }
+
+  /**
+   * Fetch the list of CHECK constraints used on a field.
+   *
+   * We introspect the database to collect the information required by field
+   * alteration.
+   *
+   * @param $table
+   *   The non-prefixed name of the table.
+   * @param $field
+   *   The name of the field.
+   * @return
+   *   An array of all the checks for the field.
+   */
+  public function queryFieldInformation($table, $field) {
+    $prefixInfo = $this->getPrefixInfo($table, TRUE);
+
+    // Split the key into schema and table for querying.
+    $schema = $prefixInfo['schema'];
+    $table_name = $prefixInfo['table'];
+
+    $this->connection->addSavepoint();
+
+    try {
+      $checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array(
+        ':schema' => $schema,
+        ':table' => $table_name,
+        ':column' => $field,
+      ));
+    }
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
+    }
+
+    $this->connection->releaseSavepoint();
+
+    $field_information = $checks->fetchCol();
+
+    return $field_information;
+  }
+
+  /**
+   * 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.
+   */
+  protected function createTableSql($name, $table) {
+    $sql_fields = array();
+    foreach ($table['fields'] as $field_name => $field) {
+      $sql_fields[] = $this->createFieldSql($field_name, $this->processField($field));
+    }
+
+    $sql_keys = array();
+    if (isset($table['primary key']) && is_array($table['primary key'])) {
+      $sql_keys[] = 'PRIMARY KEY (' . $this->createPrimaryKeySql($table['primary key']) . ')';
+    }
+    if (isset($table['unique keys']) && is_array($table['unique keys'])) {
+      foreach ($table['unique keys'] as $key_name => $key) {
+        $sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($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[] = $this->_createIndexSql($name, $key_name, $key);
+      }
+    }
+
+    // Add table comment.
+    if (!empty($table['description'])) {
+      $statements[] = 'COMMENT ON TABLE {' . $name . '} IS ' . $this->prepareComment($table['description']);
+    }
+
+    // Add column comments.
+    foreach ($table['fields'] as $field_name => $field) {
+      if (!empty($field['description'])) {
+        $statements[] = 'COMMENT ON COLUMN {' . $name . '}.' . $field_name . ' IS ' . $this->prepareComment($field['description']);
+      }
+    }
+
+    return $statements;
+  }
+
+  /**
+   * 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.
+   */
+  protected function createFieldSql($name, $spec) {
+    $sql = $name . ' ' . $spec['pgsql_type'];
+
+    if (isset($spec['type']) && $spec['type'] == 'serial') {
+      unset($spec['not null']);
+    }
+
+    if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
+      $sql .= '(' . $spec['length'] . ')';
+    }
+    elseif (isset($spec['precision']) && isset($spec['scale'])) {
+      $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
+    }
+
+    if (!empty($spec['unsigned'])) {
+      $sql .= " CHECK ($name >= 0)";
+    }
+
+    if (isset($spec['not null'])) {
+      if ($spec['not null']) {
+        $sql .= ' NOT NULL';
+      }
+      else {
+        $sql .= ' NULL';
+      }
+    }
+    if (array_key_exists('default', $spec)) {
+      $default = $this->escapeDefaultValue($spec['default']);
+      $sql .= " default $default";
+    }
+
+    return $sql;
+  }
+
+  /**
+   * Set database-engine specific properties for a field.
+   *
+   * @param $field
+   *   A field description array, as specified in the schema documentation.
+   */
+  protected function processField($field) {
+    if (!isset($field['size'])) {
+      $field['size'] = 'normal';
+    }
+
+    // Set the correct database-engine specific datatype.
+    // In case one is already provided, force it to lowercase.
+    if (isset($field['pgsql_type'])) {
+      $field['pgsql_type'] = drupal_strtolower($field['pgsql_type']);
+    }
+    else {
+      $map = $this->getFieldTypeMap();
+      $field['pgsql_type'] = $map[$field['type'] . ':' . $field['size']];
+    }
+
+    if (!empty($field['unsigned'])) {
+      // Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL,
+      // they are used to ensure a positive number is inserted and it also
+      // doubles the maximum integer size that can be stored in a field.
+      // The PostgreSQL schema in Drupal creates a check constraint
+      // to ensure that a value inserted is >= 0. To provide the extra
+      // integer capacity, here, we bump up the column field size.
+      if (!isset($map)) {
+        $map = $this->getFieldTypeMap();
+      }
+      switch ($field['pgsql_type']) {
+        case 'smallint':
+          $field['pgsql_type'] = $map['int:medium'];
+          break;
+        case 'int' :
+          $field['pgsql_type'] = $map['int:big'];
+          break;
+      }
+    }
+    if (isset($field['type']) && $field['type'] == 'serial') {
+      unset($field['not null']);
+    }
+    return $field;
+  }
+
+  /**
+   * This maps a generic data type in combination with its data size
+   * to the engine-specific data type.
+   */
+  function getFieldTypeMap() {
+    // 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 does not use drupal_static as its value never changes.
+    static $map = array(
+      'varchar:normal' => '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',
+
+      'serial:tiny' => 'serial',
+      'serial:small' => 'serial',
+      'serial:medium' => 'serial',
+      'serial:big' => 'bigserial',
+      'serial:normal' => 'serial',
+      );
+    return $map;
+  }
+
+  protected function _createKeySql($fields) {
+    $return = array();
+    foreach ($fields as $field) {
+      if (is_array($field)) {
+        $return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')';
+      }
+      else {
+        $return[] = '"' . $field . '"';
+      }
+    }
+    return implode(', ', $return);
+  }
+
+  /**
+   * Create the SQL expression for primary keys.
+   *
+   * Postgresql does not support key length. It does support fillfactor, but
+   * that requires a separate database lookup for each column in the key. The
+   * key length defined in the schema is ignored.
+   */
+  protected function createPrimaryKeySql($fields) {
+    $return = array();
+    foreach ($fields as $field) {
+      if (is_array($field)) {
+        $return[] = '"' . $field[0] . '"';
+      }
+      else {
+        $return[] = '"' . $field . '"';
+      }
+    }
+    return implode(', ', $return);
+  }
+
+  function renameTable($table, $new_name) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
+    }
+    if ($this->tableExists($new_name)) {
+      throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
+    }
+
+    // Get the schema and tablename for the old table.
+    $old_full_name = $this->connection->prefixTables('{' . $table . '}');
+    list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : array('public', $old_full_name);
+
+    // Index names and constraint names are global in PostgreSQL, so we need to
+    // rename them when renaming the table.
+    $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name));
+    foreach ($indexes as $index) {
+      if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) {
+        $index_name = $matches[1];
+        $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO ' . $this->ensureIdentifiersLength($new_name, $index_name, 'idx'));
+      }
+    }
+
+    // Now rename the table.
+    // Ensure the new table name does not include schema syntax.
+    $prefixInfo = $this->getPrefixInfo($new_name);
+    $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function copyTable($source, $destination) {
+    // @TODO The server is likely going to rename indexes and constraints
+    //   during the copy process, and it will not match our
+    //   table_name + constraint name convention anymore.
+    throw new \Exception('Not implemented, see https://drupal.org/node/2061879');
+  }
+
+  public function dropTable($table) {
+    if (!$this->tableExists($table)) {
+      return FALSE;
+    }
+
+    $this->connection->query('DROP TABLE {' . $table . '}');
+    return TRUE;
+  }
+
+  public function addField($table, $field, $spec, $new_keys = array()) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
+    }
+    if ($this->fieldExists($table, $field)) {
+      throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
+    }
+
+    $fixnull = FALSE;
+    if (!empty($spec['not null']) && !isset($spec['default'])) {
+      $fixnull = TRUE;
+      $spec['not null'] = FALSE;
+    }
+    $query = 'ALTER TABLE {' . $table . '} ADD COLUMN ';
+    $query .= $this->createFieldSql($field, $this->processField($spec));
+    $this->connection->query($query);
+    if (isset($spec['initial'])) {
+      $this->connection->update($table)
+        ->fields(array($field => $spec['initial']))
+        ->execute();
+    }
+    if ($fixnull) {
+      $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL");
+    }
+    if (isset($new_keys)) {
+      $this->_createKeys($table, $new_keys);
+    }
+    // Add column comment.
+    if (!empty($spec['description'])) {
+      $this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description']));
+    }
+  }
+
+  public function dropField($table, $field) {
+    if (!$this->fieldExists($table, $field)) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"');
+    return TRUE;
+  }
+
+  public function fieldSetDefault($table, $field, $default) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+    }
+
+    $default = $this->escapeDefaultValue($default);
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default);
+  }
+
+  public function fieldSetNoDefault($table, $field) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
+  }
+
+  public function indexExists($table, $name) {
+    // Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
+    $index_name = $this->ensureIdentifiersLength($table, $name, 'idx');
+    return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField();
+  }
+
+  /**
+   * Helper function: check if a constraint (PK, FK, UK) exists.
+   *
+   * @param $table
+   *   The name of the table.
+   * @param $name
+   *   The name of the constraint (typically 'pkey' or '[constraint]_key').
+   */
+  protected function constraintExists($table, $name) {
+    $constraint_name = $this->ensureIdentifiersLength($table, $name);
+    return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField();
+  }
+
+  public function addPrimaryKey($table, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
+    }
+    if ($this->constraintExists($table, 'pkey')) {
+      throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createPrimaryKeySql($fields) . ')');
+  }
+
+  public function dropPrimaryKey($table) {
+    if (!$this->constraintExists($table, 'pkey')) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->ensureIdentifiersLength($table, 'pkey'));
+    return TRUE;
+  }
+
+  function addUniqueKey($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->constraintExists($table, $name . '_key')) {
+      throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
+  }
+
+  public function dropUniqueKey($table, $name) {
+    if (!$this->constraintExists($table, $name . '_key')) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '"');
+    return TRUE;
+  }
+
+  public function addIndex($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->indexExists($table, $name)) {
+      throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $this->connection->query($this->_createIndexSql($table, $name, $fields));
+  }
+
+  public function dropIndex($table, $name) {
+    if (!$this->indexExists($table, $name)) {
+      return FALSE;
+    }
+
+    $this->connection->query('DROP INDEX ' . $this->ensureIdentifiersLength($table, $name, 'idx'));
+    return TRUE;
+  }
+
+  public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
+    }
+    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
+      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
+    }
+
+    $spec = $this->processField($spec);
+
+    // We need to typecast the new column to best be able to transfer the data
+    // Schema_pgsql::getFieldTypeMap() will return possibilities that are not
+    // 'cast-able' such as 'serial' - so they need to be casted int instead.
+    if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
+      $typecast = 'int';
+    }
+    else {
+      $typecast = $spec['pgsql_type'];
+    }
+
+    if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
+      $typecast .= '(' . $spec['length'] . ')';
+    }
+    elseif (isset($spec['precision']) && isset($spec['scale'])) {
+      $typecast .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
+    }
+
+    // Remove old check constraints.
+    $field_info = $this->queryFieldInformation($table, $field);
+
+    foreach ($field_info as $check) {
+      $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"');
+    }
+
+    // Remove old default.
+    $this->fieldSetNoDefault($table, $field);
+
+    // Convert field type.
+    // Usually, we do this via a simple typecast 'USING fieldname::type'. But
+    // the typecast does not work for conversions to bytea.
+    // @see http://www.postgresql.org/docs/current/static/datatype-binary.html
+    if ($spec['pgsql_type'] != 'bytea') {
+      $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast);
+    }
+    else {
+      // Do not attempt to convert a field that is bytea already.
+      $table_information = $this->queryTableInformation($table);
+      if (!in_array($field, $table_information->blob_fields)) {
+        // Convert to a bytea type by using the SQL replace() function to
+        // convert any single backslashes in the field content to double
+        // backslashes ('\' to '\\').
+        $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING decode(replace("' . $field . '"' . ", '\\', '\\\\'), 'escape');");
+      }
+    }
+
+    if (isset($spec['not null'])) {
+      if ($spec['not null']) {
+        $nullaction = 'SET NOT NULL';
+      }
+      else {
+        $nullaction = 'DROP NOT NULL';
+      }
+      $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" ' . $nullaction);
+    }
+
+    if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
+      // Type "serial" is known to PostgreSQL, but *only* during table creation,
+      // not when altering. Because of that, the sequence needs to be created
+      // and initialized by hand.
+      $seq = "{" . $table . "}_" . $field_new . "_seq";
+      $this->connection->query("CREATE SEQUENCE " . $seq);
+      // Set sequence to maximal field value to not conflict with existing
+      // entries.
+      $this->connection->query("SELECT setval('" . $seq . "', MAX(\"" . $field . '")) FROM {' . $table . "}");
+      $this->connection->query('ALTER TABLE {' . $table . '} ALTER ' . $field . ' SET DEFAULT nextval(' . $this->connection->quote($seq) . ')');
+    }
+
+    // Rename the column if necessary.
+    if ($field != $field_new) {
+      $this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"');
+    }
+
+    // Add unsigned check if necessary.
+    if (!empty($spec['unsigned'])) {
+      $this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)');
+    }
+
+    // Add default if necessary.
+    if (isset($spec['default'])) {
+      $this->fieldSetDefault($table, $field_new, $spec['default']);
+    }
+
+    // Change description if necessary.
+    if (!empty($spec['description'])) {
+      $this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description']));
+    }
+
+    if (isset($new_keys)) {
+      $this->_createKeys($table, $new_keys);
+    }
+  }
+
+  protected function _createIndexSql($table, $name, $fields) {
+    $query = 'CREATE INDEX "' . $this->ensureIdentifiersLength($table, $name, 'idx') . '" ON {' . $table . '} (';
+    $query .= $this->_createKeySql($fields) . ')';
+    return $query;
+  }
+
+  protected function _createKeys($table, $new_keys) {
+    if (isset($new_keys['primary key'])) {
+      $this->addPrimaryKey($table, $new_keys['primary key']);
+    }
+    if (isset($new_keys['unique keys'])) {
+      foreach ($new_keys['unique keys'] as $name => $fields) {
+        $this->addUniqueKey($table, $name, $fields);
+      }
+    }
+    if (isset($new_keys['indexes'])) {
+      foreach ($new_keys['indexes'] as $name => $fields) {
+        $this->addIndex($table, $name, $fields);
+      }
+    }
+  }
+
+  /**
+   * Retrieve a table or column comment.
+   */
+  public function getComment($table, $column = NULL) {
+    $info = $this->getPrefixInfo($table);
+    // Don't use {} around pg_class, pg_attribute tables.
+    if (isset($column)) {
+      return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
+    }
+    else {
+      return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
+    }
+  }
+
+  /**
+   * Calculates a base-64 encoded, PostgreSQL-safe sha-256 hash per PostgreSQL
+   * documentation: 4.1. Lexical Structure.
+   *
+   * @param $data
+   *   String to be hashed.
+   * @return string
+   *   A base-64 encoded sha-256 hash, with + and / replaced with _ and any =
+   *   padding characters removed.
+   */
+  protected function hashBase64($data) {
+    $hash = base64_encode(hash('sha256', $data, TRUE));
+    // Modify the hash so it's safe to use in PostgreSQL identifiers.
+    return strtr($hash, array('+' => '_', '/' => '_', '=' => ''));
+  }
+}
+
+/**
+ * @} End of "addtogroup schemaapi".
+ */
diff --git a/core/drivers/pgsql/src/Select.php b/core/drivers/pgsql/src/Select.php
new file mode 100644
index 0000000..20425d8
--- /dev/null
+++ b/core/drivers/pgsql/src/Select.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Select
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Query\Select as QuerySelect;
+
+/**
+ * @addtogroup database
+ * @{
+ */
+
+class Select extends QuerySelect {
+
+  public function orderRandom() {
+    $alias = $this->addExpression('RANDOM()', 'random_field');
+    $this->orderBy($alias);
+    return $this;
+  }
+
+  /**
+   * Overrides SelectQuery::orderBy().
+   *
+   * PostgreSQL adheres strictly to the SQL-92 standard and requires that when
+   * using DISTINCT or GROUP BY conditions, fields and expressions that are
+   * ordered on also need to be selected. This is a best effort implementation
+   * to handle the cases that can be automated by adding the field if it is not
+   * yet selected.
+   *
+   * @code
+   *   $query = db_select('example', 'e');
+   *   $query->join('example_revision', 'er', 'e.vid = er.vid');
+   *   $query
+   *     ->distinct()
+   *     ->fields('e')
+   *     ->orderBy('timestamp');
+   * @endcode
+   *
+   * In this query, it is not possible (without relying on the schema) to know
+   * whether timestamp belongs to example_revision and needs to be added or
+   * belongs to node and is already selected. Queries like this will need to be
+   * corrected in the original query by adding an explicit call to
+   * SelectQuery::addField() or SelectQuery::fields().
+   *
+   * Since this has a small performance impact, both by the additional
+   * processing in this function and in the database that needs to return the
+   * additional fields, this is done as an override instead of implementing it
+   * directly in SelectQuery::orderBy().
+   */
+  public function orderBy($field, $direction = 'ASC') {
+    // Call parent function to order on this.
+    $return = parent::orderBy($field, $direction);
+
+    if ($this->hasTag('entity_query')) {
+      return $return;
+    }
+
+    // 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 ambiguous 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 handled 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;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute() {
+    $this->connection->addSavepoint();
+    try {
+      $result = parent::execute();
+    }
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
+    }
+    $this->connection->releaseSavepoint();
+
+    return $result;
+  }
+}
+
+/**
+ * @} End of "addtogroup database".
+ */
diff --git a/core/drivers/pgsql/src/Transaction.php b/core/drivers/pgsql/src/Transaction.php
new file mode 100644
index 0000000..8e8eea2
--- /dev/null
+++ b/core/drivers/pgsql/src/Transaction.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Transaction
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Transaction as DatabaseTransaction;
+
+class Transaction extends DatabaseTransaction { }
diff --git a/core/drivers/pgsql/src/Truncate.php b/core/drivers/pgsql/src/Truncate.php
new file mode 100644
index 0000000..3db4599
--- /dev/null
+++ b/core/drivers/pgsql/src/Truncate.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Truncate
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Query\Truncate as QueryTruncate;
+
+class Truncate extends QueryTruncate { }
diff --git a/core/drivers/pgsql/src/Update.php b/core/drivers/pgsql/src/Update.php
new file mode 100644
index 0000000..a04363d
--- /dev/null
+++ b/core/drivers/pgsql/src/Update.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\pgsql\Update
+ */
+
+namespace Drupal\pgsql;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Query\Update as QueryUpdate;
+use Drupal\Core\Database\Query\SelectInterface;
+
+class Update extends QueryUpdate {
+
+  public function execute() {
+    $max_placeholder = 0;
+    $blobs = array();
+    $blob_count = 0;
+
+    // Because we filter $fields the same way here and in __toString(), the
+    // placeholders will all match up properly.
+    $stmt = $this->connection->prepareQuery((string) $this);
+
+    // Fetch the list of blobs and sequences used on that table.
+    $table_information = $this->connection->schema()->queryTableInformation($this->table);
+
+    // Expressions take priority over literal fields, so we process those first
+    // and remove any literal fields that conflict.
+    $fields = $this->fields;
+    foreach ($this->expressionFields as $field => $data) {
+      if (!empty($data['arguments'])) {
+        foreach ($data['arguments'] as $placeholder => $argument) {
+          // We assume that an expression will never happen on a BLOB field,
+          // which is a fairly safe assumption to make since in most cases
+          // it would be an invalid query anyway.
+          $stmt->bindParam($placeholder, $data['arguments'][$placeholder]);
+        }
+      }
+      if ($data['expression'] instanceof SelectInterface) {
+        $data['expression']->compile($this->connection, $this);
+        $select_query_arguments = $data['expression']->arguments();
+        foreach ($select_query_arguments as $placeholder => $argument) {
+          $stmt->bindParam($placeholder, $select_query_arguments[$placeholder]);
+        }
+      }
+      unset($fields[$field]);
+    }
+
+    foreach ($fields as $field => $value) {
+      $placeholder = ':db_update_placeholder_' . ($max_placeholder++);
+
+      if (isset($table_information->blob_fields[$field])) {
+        $blobs[$blob_count] = fopen('php://memory', 'a');
+        fwrite($blobs[$blob_count], $value);
+        rewind($blobs[$blob_count]);
+        $stmt->bindParam($placeholder, $blobs[$blob_count], \PDO::PARAM_LOB);
+        ++$blob_count;
+      }
+      else {
+        $stmt->bindParam($placeholder, $fields[$field]);
+      }
+    }
+
+    if (count($this->condition)) {
+      $this->condition->compile($this->connection, $this);
+
+      $arguments = $this->condition->arguments();
+      foreach ($arguments as $placeholder => $value) {
+        $stmt->bindParam($placeholder, $arguments[$placeholder]);
+      }
+    }
+
+    $options = $this->queryOptions;
+    $options['already_prepared'] = TRUE;
+    $options['return'] = Database::RETURN_AFFECTED;
+
+    $this->connection->addSavepoint();
+    try {
+      $result = $this->connection->query($stmt, array(), $options);
+      $this->connection->releaseSavepoint();
+      return $result;
+    }
+    catch (\Exception $e) {
+      $this->connection->rollbackSavepoint();
+      throw $e;
+    }
+  }
+
+}
diff --git a/core/drivers/sqlite/sqlite.info.yml b/core/drivers/sqlite/sqlite.info.yml
new file mode 100644
index 0000000..288dfa4
--- /dev/null
+++ b/core/drivers/sqlite/sqlite.info.yml
@@ -0,0 +1,3 @@
+type: driver
+core: 8.x
+name: SQLite
diff --git a/core/drivers/sqlite/src/Connection.php b/core/drivers/sqlite/src/Connection.php
new file mode 100644
index 0000000..3fb90ac
--- /dev/null
+++ b/core/drivers/sqlite/src/Connection.php
@@ -0,0 +1,411 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Connection
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseNotFoundException;
+use Drupal\Core\Database\TransactionNoActiveException;
+use Drupal\Core\Database\TransactionNameNonUniqueException;
+use Drupal\Core\Database\TransactionCommitFailedException;
+use Drupal\sqlite\Statement;
+use Drupal\Core\Database\Connection as DatabaseConnection;
+
+/**
+ * Specific SQLite implementation of DatabaseConnection.
+ */
+class Connection extends DatabaseConnection {
+
+  /**
+   * Whether this database connection supports savepoints.
+   *
+   * Version of sqlite lower then 3.6.8 can't use savepoints.
+   * See http://www.sqlite.org/releaselog/3_6_8.html
+   *
+   * @var boolean
+   */
+  protected $savepointSupport = FALSE;
+
+  /**
+   * Error code for "Unable to open database file" error.
+   */
+  const DATABASE_NOT_FOUND = 14;
+
+  /**
+   * Whether or not the active transaction (if any) will be rolled back.
+   *
+   * @var boolean
+   */
+  protected $willRollback;
+
+  /**
+   * All databases attached to the current database. This is used to allow
+   * prefixes to be safely handled without locking the table
+   *
+   * @var array
+   */
+  protected $attachedDatabases = array();
+
+  /**
+   * Whether or not a table has been dropped this request: the destructor will
+   * only try to get rid of unnecessary databases if there is potential of them
+   * being empty.
+   *
+   * This variable is set to public because Schema needs to
+   * access it. However, it should not be manually set.
+   *
+   * @var boolean
+   */
+  var $tableDropped = FALSE;
+
+  /**
+   * Constructs a \Drupal\sqlite\Connection object.
+   */
+  public function __construct(\PDO $connection, array $connection_options) {
+    parent::__construct($connection, $connection_options);
+
+    // We don't need a specific PDOStatement class here, we simulate it below.
+    $this->statementClass = NULL;
+
+    // This driver defaults to transaction support, except if explicitly passed FALSE.
+    $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
+
+    $this->connectionOptions = $connection_options;
+
+    // Attach one database for each registered prefix.
+    $prefixes = $this->prefixes;
+    foreach ($prefixes as &$prefix) {
+      // Empty prefix means query the main database -- no need to attach anything.
+      if (!empty($prefix)) {
+        // Only attach the database once.
+        if (!isset($this->attachedDatabases[$prefix])) {
+          $this->attachedDatabases[$prefix] = $prefix;
+          $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix));
+        }
+
+        // Add a ., so queries become prefix.table, which is proper syntax for
+        // querying an attached database.
+        $prefix .= '.';
+      }
+    }
+    // Regenerate the prefixes replacement table.
+    $this->setPrefix($prefixes);
+
+    // Detect support for SAVEPOINT.
+    $version = $this->query('SELECT sqlite_version()')->fetchField();
+    $this->savepointSupport = (version_compare($version, '3.6.8') >= 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function open(array &$connection_options = array()) {
+    // Allow PDO options to be overridden.
+    $connection_options += array(
+      'pdo' => array(),
+    );
+    $connection_options['pdo'] += array(
+      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+      // Convert numeric values to strings when fetching.
+      \PDO::ATTR_STRINGIFY_FETCHES => TRUE,
+    );
+    $pdo = new \PDO('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
+
+    // Create functions needed by SQLite.
+    $pdo->sqliteCreateFunction('if', array(__CLASS__, 'sqlFunctionIf'));
+    $pdo->sqliteCreateFunction('greatest', array(__CLASS__, 'sqlFunctionGreatest'));
+    $pdo->sqliteCreateFunction('pow', 'pow', 2);
+    $pdo->sqliteCreateFunction('length', 'strlen', 1);
+    $pdo->sqliteCreateFunction('md5', 'md5', 1);
+    $pdo->sqliteCreateFunction('concat', array(__CLASS__, 'sqlFunctionConcat'));
+    $pdo->sqliteCreateFunction('substring', array(__CLASS__, 'sqlFunctionSubstring'), 3);
+    $pdo->sqliteCreateFunction('substring_index', array(__CLASS__, 'sqlFunctionSubstringIndex'), 3);
+    $pdo->sqliteCreateFunction('rand', array(__CLASS__, 'sqlFunctionRand'));
+    $pdo->sqliteCreateFunction('regexp', array(__CLASS__, 'sqlFunctionRegexp'));
+
+    // Execute sqlite init_commands.
+    if (isset($connection_options['init_commands'])) {
+      $pdo->exec(implode('; ', $connection_options['init_commands']));
+    }
+
+    return $pdo;
+  }
+
+
+  /**
+   * Destructor for the SQLite connection.
+   *
+   * We prune empty databases on destruct, but only if tables have been
+   * dropped. This is especially needed when running the test suite, which
+   * creates and destroy databases several times in a row.
+   */
+  public function __destruct() {
+    if ($this->tableDropped && !empty($this->attachedDatabases)) {
+      foreach ($this->attachedDatabases as $prefix) {
+        // Check if the database is now empty, ignore the internal SQLite tables.
+        try {
+          $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
+
+          // We can prune the database file if it doesn't have any tables.
+          if ($count == 0) {
+            // Detach the database.
+            $this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
+            // Destroy the database file.
+            unlink($this->connectionOptions['database'] . '-' . $prefix);
+          }
+        }
+        catch (\Exception $e) {
+          // Ignore the exception and continue. There is nothing we can do here
+          // to report the error or fail safe.
+        }
+      }
+    }
+  }
+
+  /**
+   * SQLite compatibility implementation for the IF() SQL function.
+   */
+  public static function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
+    return $condition ? $expr1 : $expr2;
+  }
+
+  /**
+   * SQLite compatibility implementation for the GREATEST() SQL function.
+   */
+  public static function sqlFunctionGreatest() {
+    $args = func_get_args();
+    foreach ($args as $v) {
+      if (!isset($v)) {
+        unset($args);
+      }
+    }
+    if (count($args)) {
+      return max($args);
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  /**
+   * SQLite compatibility implementation for the CONCAT() SQL function.
+   */
+  public static function sqlFunctionConcat() {
+    $args = func_get_args();
+    return implode('', $args);
+  }
+
+  /**
+   * SQLite compatibility implementation for the SUBSTRING() SQL function.
+   */
+  public static function sqlFunctionSubstring($string, $from, $length) {
+    return substr($string, $from - 1, $length);
+  }
+
+  /**
+   * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
+   */
+  public static function sqlFunctionSubstringIndex($string, $delimiter, $count) {
+    // If string is empty, simply return an empty string.
+    if (empty($string)) {
+      return '';
+    }
+    $end = 0;
+    for ($i = 0; $i < $count; $i++) {
+      $end = strpos($string, $delimiter, $end + 1);
+      if ($end === FALSE) {
+        $end = strlen($string);
+      }
+    }
+    return substr($string, 0, $end);
+  }
+
+  /**
+   * SQLite compatibility implementation for the RAND() SQL function.
+   */
+  public static function sqlFunctionRand($seed = NULL) {
+    if (isset($seed)) {
+      mt_srand($seed);
+    }
+    return mt_rand() / mt_getrandmax();
+  }
+
+  /**
+   * SQLite compatibility implementation for the REGEXP SQL operator.
+   *
+   * The REGEXP operator is a special syntax for the regexp() user function.
+   */
+  public static function sqlFunctionRegexp($string, $pattern) {
+    return preg_match('#' . str_replace('#', '\#', $pattern) . '#i', $string);
+  }
+
+  /**
+   * SQLite-specific implementation of DatabaseConnection::prepare().
+   *
+   * We don't use prepared statements at all at this stage. We just create
+   * a Statement object, that will create a PDOStatement
+   * using the semi-private PDOPrepare() method below.
+   */
+  public function prepare($statement, array $driver_options = array()) {
+    return new Statement($this->connection, $this, $statement, $driver_options);
+  }
+
+  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
+    return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
+  }
+
+  public function queryTemporary($query, array $args = array(), array $options = array()) {
+    // Generate a new temporary table name and protect it from prefixing.
+    // SQLite requires that temporary tables to be non-qualified.
+    $tablename = $this->generateTemporaryTableName();
+    $prefixes = $this->prefixes;
+    $prefixes[$tablename] = '';
+    $this->setPrefix($prefixes);
+
+    $this->query('CREATE TEMPORARY TABLE ' . $tablename . ' AS ' . $query, $args, $options);
+    return $tablename;
+  }
+
+  public function driver() {
+    return 'sqlite';
+  }
+
+  public function databaseType() {
+    return 'sqlite';
+  }
+
+  /**
+   * Overrides \Drupal\Core\Database\Connection::createDatabase().
+   *
+   * @param string $database
+   *   The name of the database to create.
+   *
+   * @throws \Drupal\Core\Database\DatabaseNotFoundException
+   */
+  public function createDatabase($database) {
+    // Verify the database is writable.
+    $db_directory = new \SplFileInfo(dirname($database));
+    if (!$db_directory->isDir() && !drupal_mkdir($db_directory->getPathName(), 0755, TRUE)) {
+      throw new DatabaseNotFoundException('Unable to create database directory ' . $db_directory->getPathName());
+    }
+  }
+
+  public function mapConditionOperator($operator) {
+    // We don't want to override any of the defaults.
+    static $specials = array(
+      'LIKE' => array('postfix' => " ESCAPE '\\'"),
+      'NOT LIKE' => array('postfix' => " ESCAPE '\\'"),
+    );
+    return isset($specials[$operator]) ? $specials[$operator] : NULL;
+  }
+
+  public function nextId($existing_id = 0) {
+    $this->startTransaction();
+    // We can safely use literal queries here instead of the slower query
+    // builder because if a given database breaks here then it can simply
+    // override nextId. However, this is unlikely as we deal with short strings
+    // and integers and no known databases require special handling for those
+    // simple cases. If another transaction wants to write the same row, it will
+    // wait until this transaction commits. Also, the return value needs to be
+    // set to RETURN_AFFECTED as if it were a real update() query otherwise it
+    // is not possible to get the row count properly.
+    $affected = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array(
+      ':existing_id' => $existing_id,
+    ), array('return' => Database::RETURN_AFFECTED));
+    if (!$affected) {
+      $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array(
+        ':existing_id' => $existing_id,
+      ));
+    }
+    // The transaction gets committed when the transaction object gets destroyed
+    // because it gets out of scope.
+    return $this->query('SELECT value FROM {sequences}')->fetchField();
+  }
+
+  public function rollback($savepoint_name = 'drupal_transaction') {
+    if ($this->savepointSupport) {
+      return parent::rollBack($savepoint_name);
+    }
+
+    if (!$this->inTransaction()) {
+      throw new TransactionNoActiveException();
+    }
+    // A previous rollback to an earlier savepoint may mean that the savepoint
+    // in question has already been rolled back.
+    if (!isset($this->transactionLayers[$savepoint_name])) {
+      return;
+    }
+
+    // We need to find the point we're rolling back to, all other savepoints
+    // before are no longer needed.
+    while ($savepoint = array_pop($this->transactionLayers)) {
+      if ($savepoint == $savepoint_name) {
+        // Mark whole stack of transactions as needed roll back.
+        $this->willRollback = TRUE;
+        // If it is the last the transaction in the stack, then it is not a
+        // savepoint, it is the transaction itself so we will need to roll back
+        // the transaction rather than a savepoint.
+        if (empty($this->transactionLayers)) {
+          break;
+        }
+        return;
+      }
+    }
+    if ($this->supportsTransactions()) {
+      $this->connection->rollBack();
+    }
+  }
+
+  public function pushTransaction($name) {
+    if ($this->savepointSupport) {
+      return parent::pushTransaction($name);
+    }
+    if (!$this->supportsTransactions()) {
+      return;
+    }
+    if (isset($this->transactionLayers[$name])) {
+      throw new TransactionNameNonUniqueException($name . " is already in use.");
+    }
+    if (!$this->inTransaction()) {
+      $this->connection->beginTransaction();
+    }
+    $this->transactionLayers[$name] = $name;
+  }
+
+  public function popTransaction($name) {
+    if ($this->savepointSupport) {
+      return parent::popTransaction($name);
+    }
+    if (!$this->supportsTransactions()) {
+      return;
+    }
+    if (!$this->inTransaction()) {
+      throw new TransactionNoActiveException();
+    }
+
+    // Commit everything since SAVEPOINT $name.
+    while($savepoint = array_pop($this->transactionLayers)) {
+      if ($savepoint != $name) continue;
+
+      // If there are no more layers left then we should commit or rollback.
+      if (empty($this->transactionLayers)) {
+        // If there was any rollback() we should roll back whole transaction.
+        if ($this->willRollback) {
+          $this->willRollback = FALSE;
+          $this->connection->rollBack();
+        }
+        elseif (!$this->connection->commit()) {
+          throw new TransactionCommitFailedException();
+        }
+      }
+      else {
+        break;
+      }
+    }
+  }
+
+}
diff --git a/core/drivers/sqlite/src/Delete.php b/core/drivers/sqlite/src/Delete.php
new file mode 100644
index 0000000..278597a
--- /dev/null
+++ b/core/drivers/sqlite/src/Delete.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Delete
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Query\Delete as QueryDelete;
+
+/**
+ * SQLite specific implementation of DeleteQuery.
+ *
+ * When the WHERE is omitted from a DELETE statement and the table being deleted
+ * has no triggers, SQLite uses an optimization to erase the entire table content
+ * without having to visit each row of the table individually.
+ *
+ * Prior to SQLite 3.6.5, SQLite does not return the actual number of rows deleted
+ * by that optimized "truncate" optimization.
+ */
+class Delete extends QueryDelete {
+  public function execute() {
+    if (!count($this->condition)) {
+      $total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField();
+      parent::execute();
+      return $total_rows;
+    }
+    else {
+      return parent::execute();
+    }
+  }
+}
diff --git a/core/drivers/sqlite/src/Insert.php b/core/drivers/sqlite/src/Insert.php
new file mode 100644
index 0000000..752462d
--- /dev/null
+++ b/core/drivers/sqlite/src/Insert.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Insert
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Query\Insert as QueryInsert;
+
+/**
+ * SQLite specific implementation of InsertQuery.
+ *
+ * We ignore all the default fields and use the clever SQLite syntax:
+ *   INSERT INTO table DEFAULT VALUES
+ * for degenerated "default only" queries.
+ */
+class Insert extends QueryInsert {
+
+  public function execute() {
+    if (!$this->preExecute()) {
+      return NULL;
+    }
+    if (count($this->insertFields)) {
+      return parent::execute();
+    }
+    else {
+      return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', array(), $this->queryOptions);
+    }
+  }
+
+  public function __toString() {
+    // Create a sanitized comment string to prepend to the query.
+    $comments = $this->connection->makeComment($this->comments);
+
+    // Produce as many generic placeholders as necessary.
+    $placeholders = array_fill(0, count($this->insertFields), '?');
+
+    // If we're selecting from a SelectQuery, finish building the query and
+    // pass it back, as any remaining options are irrelevant.
+    if (!empty($this->fromQuery)) {
+      $insert_fields_string = $this->insertFields ? ' (' . implode(', ', $this->insertFields) . ') ' : ' ';
+      return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
+    }
+
+    return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')';
+  }
+
+}
diff --git a/core/drivers/sqlite/src/Install/Tasks.php b/core/drivers/sqlite/src/Install/Tasks.php
new file mode 100644
index 0000000..ad749b4
--- /dev/null
+++ b/core/drivers/sqlite/src/Install/Tasks.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Install\Tasks
+ */
+
+namespace Drupal\sqlite\Install;
+
+use Drupal\Core\Database\Database;
+use Drupal\sqlite\Connection;
+use Drupal\Core\Database\DatabaseNotFoundException;
+use Drupal\Core\Database\Install\Tasks as InstallTasks;
+
+/**
+ * Specifies installation tasks for SQLite databases.
+ */
+class Tasks extends InstallTasks {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $pdoDriver = 'sqlite';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function name() {
+    return t('SQLite');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function minimumVersion() {
+    // @todo Consider upping to 3.6.8 in Drupal 8 to get SAVEPOINT support.
+    return '3.3.7';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormOptions(array $database) {
+    $form = parent::getFormOptions($database);
+
+    // Remove the options that only apply to client/server style databases.
+    unset($form['username'], $form['password'], $form['advanced_options']['host'], $form['advanced_options']['port']);
+
+    // Make the text more accurate for SQLite.
+    $form['database']['#title'] = t('Database file');
+    $form['database']['#description'] = t('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
+    $default_database = conf_path(FALSE) . '/files/.ht.sqlite';
+    $form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database'];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function connect() {
+    try {
+      // This doesn't actually test the connection.
+      db_set_active();
+      // Now actually do a check.
+      Database::getConnection();
+      $this->pass('Drupal can CONNECT to the database ok.');
+    }
+    catch (\Exception $e) {
+      // Attempt to create the database if it is not found.
+      if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
+        // Remove the database string from connection info.
+        $connection_info = Database::getConnectionInfo();
+        $database = $connection_info['default']['database'];
+
+        // We cannot use file_directory_temp() here because we haven't yet
+        // successfully connected to the database.
+        $connection_info['default']['database'] = drupal_tempnam(sys_get_temp_dir(), 'sqlite');
+
+        // In order to change the Database::$databaseInfo array, need to remove
+        // the active connection, then re-add it with the new info.
+        Database::removeConnection('default');
+        Database::addConnectionInfo('default', 'default', $connection_info['default']);
+
+        try {
+          Database::getConnection()->createDatabase($database);
+          Database::closeConnection();
+
+          // Now, restore the database config.
+          Database::removeConnection('default');
+          $connection_info['default']['database'] = $database;
+          Database::addConnectionInfo('default', 'default', $connection_info['default']);
+
+          // Check the database connection.
+          Database::getConnection();
+          $this->pass('Drupal can CONNECT to the database ok.');
+        }
+        catch (DatabaseNotFoundException $e) {
+          // Still no dice; probably a permission issue. Raise the error to the
+          // installer.
+          $this->fail(t('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
+        }
+      }
+      else {
+        // Database connection failed for some other reason than the database
+        // not existing.
+        $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+}
diff --git a/core/drivers/sqlite/src/Merge.php b/core/drivers/sqlite/src/Merge.php
new file mode 100644
index 0000000..28be123
--- /dev/null
+++ b/core/drivers/sqlite/src/Merge.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Merge
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Query\Merge as QueryMerge;
+
+class Merge extends QueryMerge { }
diff --git a/core/drivers/sqlite/src/Schema.php b/core/drivers/sqlite/src/Schema.php
new file mode 100644
index 0000000..b4ed5dc
--- /dev/null
+++ b/core/drivers/sqlite/src/Schema.php
@@ -0,0 +1,703 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Schema
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Database\SchemaObjectExistsException;
+use Drupal\Core\Database\SchemaObjectDoesNotExistException;
+use Drupal\Core\Database\Schema as DatabaseSchema;
+
+/**
+ * @ingroup schemaapi
+ * @{
+ */
+
+class Schema extends DatabaseSchema {
+
+  /**
+   * Override DatabaseSchema::$defaultSchema
+   */
+  protected $defaultSchema = 'main';
+
+  public function tableExists($table) {
+    $info = $this->getPrefixInfo($table);
+
+    // Don't use {} around sqlite_master table.
+    return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', array(':type' => 'table', ':name' => $info['table']))->fetchField();
+  }
+
+  public function fieldExists($table, $column) {
+    $schema = $this->introspectSchema($table);
+    return !empty($schema['fields'][$column]);
+  }
+
+  /**
+   * 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.
+   */
+  public function createTableSql($name, $table) {
+    $sql = array();
+    $sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n);\n";
+    return array_merge($sql, $this->createIndexSql($name, $table));
+  }
+
+  /**
+   * Build the SQL expression for indexes.
+   */
+  protected function createIndexSql($tablename, $schema) {
+    $sql = array();
+    $info = $this->getPrefixInfo($tablename);
+    if (!empty($schema['unique keys'])) {
+      foreach ($schema['unique keys'] as $key => $fields) {
+        $sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
+      }
+    }
+    if (!empty($schema['indexes'])) {
+      foreach ($schema['indexes'] as $key => $fields) {
+        $sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
+      }
+    }
+    return $sql;
+  }
+
+  /**
+   * Build the SQL expression for creating columns.
+   */
+  protected function createColumnsSql($tablename, $schema) {
+    $sql_array = array();
+
+    // Add the SQL statement for each field.
+    foreach ($schema['fields'] as $name => $field) {
+      if (isset($field['type']) && $field['type'] == 'serial') {
+        if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) {
+          unset($schema['primary key'][$key]);
+        }
+      }
+      $sql_array[] = $this->createFieldSql($name, $this->processField($field));
+    }
+
+    // Process keys.
+    if (!empty($schema['primary key'])) {
+      $sql_array[] = " PRIMARY KEY (" . $this->createKeySql($schema['primary key']) . ")";
+    }
+
+    return implode(", \n", $sql_array);
+  }
+
+  /**
+   * Build the SQL expression for keys.
+   */
+  protected function createKeySql($fields) {
+    $return = array();
+    foreach ($fields as $field) {
+      if (is_array($field)) {
+        $return[] = $field[0];
+      }
+      else {
+        $return[] = $field;
+      }
+    }
+    return implode(', ', $return);
+  }
+
+  /**
+   * Set database-engine specific properties for a field.
+   *
+   * @param $field
+   *   A field description array, as specified in the schema documentation.
+   */
+  protected function processField($field) {
+    if (!isset($field['size'])) {
+      $field['size'] = 'normal';
+    }
+
+    // Set the correct database-engine specific datatype.
+    // In case one is already provided, force it to uppercase.
+    if (isset($field['sqlite_type'])) {
+      $field['sqlite_type'] = drupal_strtoupper($field['sqlite_type']);
+    }
+    else {
+      $map = $this->getFieldTypeMap();
+      $field['sqlite_type'] = $map[$field['type'] . ':' . $field['size']];
+    }
+
+    if (isset($field['type']) && $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_processField().
+   *
+   * @param $name
+   *    Name of the field.
+   * @param $spec
+   *    The field specification, as per the schema data structure format.
+   */
+  protected function createFieldSql($name, $spec) {
+    if (!empty($spec['auto_increment'])) {
+      $sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT";
+      if (!empty($spec['unsigned'])) {
+        $sql .= ' CHECK (' . $name . '>= 0)';
+      }
+    }
+    else {
+      $sql = $name . ' ' . $spec['sqlite_type'];
+
+      if (in_array($spec['sqlite_type'], array('VARCHAR', 'TEXT')) && isset($spec['length'])) {
+        $sql .= '(' . $spec['length'] . ')';
+      }
+
+      if (isset($spec['not null'])) {
+        if ($spec['not null']) {
+          $sql .= ' NOT NULL';
+        }
+        else {
+          $sql .= ' NULL';
+        }
+      }
+
+      if (!empty($spec['unsigned'])) {
+        $sql .= ' CHECK (' . $name . '>= 0)';
+      }
+
+      if (isset($spec['default'])) {
+        if (is_string($spec['default'])) {
+          $spec['default'] = $this->connection->quote($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.
+   */
+  public function getFieldTypeMap() {
+    // 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 does not use drupal_static as its value never changes.
+    static $map = array(
+      'varchar:normal'  => 'VARCHAR',
+      'char:normal'     => 'CHAR',
+
+      'text:tiny'       => 'TEXT',
+      'text:small'      => 'TEXT',
+      'text:medium'     => 'TEXT',
+      'text:big'        => 'TEXT',
+      'text:normal'     => 'TEXT',
+
+      'serial:tiny'     => 'INTEGER',
+      'serial:small'    => 'INTEGER',
+      'serial:medium'   => 'INTEGER',
+      'serial:big'      => 'INTEGER',
+      'serial:normal'   => 'INTEGER',
+
+      'int:tiny'        => 'INTEGER',
+      'int:small'       => 'INTEGER',
+      'int:medium'      => 'INTEGER',
+      'int:big'         => 'INTEGER',
+      'int:normal'      => 'INTEGER',
+
+      'float:tiny'      => 'FLOAT',
+      'float:small'     => 'FLOAT',
+      'float:medium'    => 'FLOAT',
+      'float:big'       => 'FLOAT',
+      'float:normal'    => 'FLOAT',
+
+      'numeric:normal'  => 'NUMERIC',
+
+      'blob:big'        => 'BLOB',
+      'blob:normal'     => 'BLOB',
+    );
+    return $map;
+  }
+
+  public function renameTable($table, $new_name) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
+    }
+    if ($this->tableExists($new_name)) {
+      throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
+    }
+
+    $schema = $this->introspectSchema($table);
+
+    // SQLite doesn't allow you to rename tables outside of the current
+    // database. So the syntax '...RENAME TO database.table' would fail.
+    // So we must determine the full table name here rather than surrounding
+    // the table with curly braces in case the db_prefix contains a reference
+    // to a database outside of our existing database.
+    $info = $this->getPrefixInfo($new_name);
+    $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']);
+
+    // Drop the indexes, there is no RENAME INDEX command in SQLite.
+    if (!empty($schema['unique keys'])) {
+      foreach ($schema['unique keys'] as $key => $fields) {
+        $this->dropIndex($table, $key);
+      }
+    }
+    if (!empty($schema['indexes'])) {
+      foreach ($schema['indexes'] as $index => $fields) {
+        $this->dropIndex($table, $index);
+      }
+    }
+
+    // Recreate the indexes.
+    $statements = $this->createIndexSql($new_name, $schema);
+    foreach ($statements as $statement) {
+      $this->connection->query($statement);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function copyTable($source, $destination) {
+    if (!$this->tableExists($source)) {
+      throw new SchemaObjectDoesNotExistException(String::format("Cannot copy @source to @destination: table @source doesn't exist.", array('@source' => $source, '@destination' => $destination)));
+    }
+    if ($this->tableExists($destination)) {
+      throw new SchemaObjectExistsException(String::format("Cannot copy @source to @destination: table @destination already exists.", array('@source' => $source, '@destination' => $destination)));
+    }
+
+    $this->createTable($destination, $this->introspectSchema($source));
+  }
+
+  public function dropTable($table) {
+    if (!$this->tableExists($table)) {
+      return FALSE;
+    }
+    $this->connection->tableDropped = TRUE;
+    $this->connection->query('DROP TABLE {' . $table . '}');
+    return TRUE;
+  }
+
+  public function addField($table, $field, $specification, $keys_new = array()) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
+    }
+    if ($this->fieldExists($table, $field)) {
+      throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
+    }
+
+    // SQLite doesn't have a full-featured ALTER TABLE statement. It only
+    // supports adding new fields to a table, in some simple cases. In most
+    // cases, we have to create a new table and copy the data over.
+    if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) {
+      // When we don't have to create new keys and we are not creating a
+      // NOT NULL column without a default value, we can use the quicker version.
+      $query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification));
+      $this->connection->query($query);
+
+      // Apply the initial value if set.
+      if (isset($specification['initial'])) {
+        $this->connection->update($table)
+          ->fields(array($field => $specification['initial']))
+          ->execute();
+      }
+    }
+    else {
+      // We cannot add the field directly. Use the slower table alteration
+      // method, starting from the old schema.
+      $old_schema = $this->introspectSchema($table);
+      $new_schema = $old_schema;
+
+      // Add the new field.
+      $new_schema['fields'][$field] = $specification;
+
+      // Build the mapping between the old fields and the new fields.
+      $mapping = array();
+      if (isset($specification['initial'])) {
+        // If we have a initial value, copy it over.
+        $mapping[$field] = array(
+          'expression' => ':newfieldinitial',
+          'arguments' => array(':newfieldinitial' => $specification['initial']),
+        );
+      }
+      else {
+        // Else use the default of the field.
+        $mapping[$field] = NULL;
+      }
+
+      // Add the new indexes.
+      $new_schema += $keys_new;
+
+      $this->alterTable($table, $old_schema, $new_schema, $mapping);
+    }
+  }
+
+  /**
+   * Create a table with a new schema containing the old content.
+   *
+   * As SQLite does not support ALTER TABLE (with a few exceptions) it is
+   * necessary to create a new table and copy over the old content.
+   *
+   * @param $table
+   *   Name of the table to be altered.
+   * @param $old_schema
+   *   The old schema array for the table.
+   * @param $new_schema
+   *   The new schema array for the table.
+   * @param $mapping
+   *   An optional mapping between the fields of the old specification and the
+   *   fields of the new specification. An associative array, whose keys are
+   *   the fields of the new table, and values can take two possible forms:
+   *     - a simple string, which is interpreted as the name of a field of the
+   *       old table,
+   *     - an associative array with two keys 'expression' and 'arguments',
+   *       that will be used as an expression field.
+   */
+  protected function alterTable($table, $old_schema, $new_schema, array $mapping = array()) {
+    $i = 0;
+    do {
+      $new_table = $table . '_' . $i++;
+    } while ($this->tableExists($new_table));
+
+    $this->createTable($new_table, $new_schema);
+
+    // Build a SQL query to migrate the data from the old table to the new.
+    $select = $this->connection->select($table);
+
+    // Complete the mapping.
+    $possible_keys = array_keys($new_schema['fields']);
+    $mapping += array_combine($possible_keys, $possible_keys);
+
+    // Now add the fields.
+    foreach ($mapping as $field_alias => $field_source) {
+      // Just ignore this field (ie. use it's default value).
+      if (!isset($field_source)) {
+        continue;
+      }
+
+      if (is_array($field_source)) {
+        $select->addExpression($field_source['expression'], $field_alias, $field_source['arguments']);
+      }
+      else {
+        $select->addField($table, $field_source, $field_alias);
+      }
+    }
+
+    // Execute the data migration query.
+    $this->connection->insert($new_table)
+      ->from($select)
+      ->execute();
+
+    $old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField();
+    $new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField();
+    if ($old_count == $new_count) {
+      $this->dropTable($table);
+      $this->renameTable($new_table, $table);
+    }
+  }
+
+  /**
+   * Find out the schema of a table.
+   *
+   * This function uses introspection methods provided by the database to
+   * create a schema array. This is useful, for example, during update when
+   * the old schema is not available.
+   *
+   * @param $table
+   *   Name of the table.
+   * @return
+   *   An array representing the schema, from drupal_get_schema().
+   * @see drupal_get_schema()
+   */
+  protected function introspectSchema($table) {
+    $mapped_fields = array_flip($this->getFieldTypeMap());
+    $schema = array(
+      'fields' => array(),
+      'primary key' => array(),
+      'unique keys' => array(),
+      'indexes' => array(),
+    );
+
+    $info = $this->getPrefixInfo($table);
+    $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.table_info(' . $info['table'] . ')');
+    foreach ($result as $row) {
+      if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) {
+        $type = $matches[1];
+        $length = $matches[2];
+      }
+      else {
+        $type = $row->type;
+        $length = NULL;
+      }
+      if (isset($mapped_fields[$type])) {
+        list($type, $size) = explode(':', $mapped_fields[$type]);
+        $schema['fields'][$row->name] = array(
+          'type' => $type,
+          'size' => $size,
+          'not null' => !empty($row->notnull),
+          'default' => trim($row->dflt_value, "'"),
+        );
+        if ($length) {
+          $schema['fields'][$row->name]['length'] = $length;
+        }
+        if ($row->pk) {
+          $schema['primary key'][] = $row->name;
+        }
+      }
+      else {
+        new \Exception("Unable to parse the column type " . $row->type);
+      }
+    }
+    $indexes = array();
+    $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
+    foreach ($result as $row) {
+      if (strpos($row->name, 'sqlite_autoindex_') !== 0) {
+        $indexes[] = array(
+          'schema_key' => $row->unique ? 'unique keys' : 'indexes',
+          'name' => $row->name,
+        );
+      }
+    }
+    foreach ($indexes as $index) {
+      $name = $index['name'];
+      // Get index name without prefix.
+      $index_name = substr($name, strlen($info['table']) + 1);
+      $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $name . ')');
+      foreach ($result as $row) {
+        $schema[$index['schema_key']][$index_name][] = $row->name;
+      }
+    }
+    return $schema;
+  }
+
+  public function dropField($table, $field) {
+    if (!$this->fieldExists($table, $field)) {
+      return FALSE;
+    }
+
+    $old_schema = $this->introspectSchema($table);
+    $new_schema = $old_schema;
+
+    unset($new_schema['fields'][$field]);
+    foreach ($new_schema['indexes'] as $index => $fields) {
+      foreach ($fields as $key => $field_name) {
+        if ($field_name == $field) {
+          unset($new_schema['indexes'][$index][$key]);
+        }
+      }
+      // If this index has no more fields then remove it.
+      if (empty($new_schema['indexes'][$index])) {
+        unset($new_schema['indexes'][$index]);
+      }
+    }
+    $this->alterTable($table, $old_schema, $new_schema);
+    return TRUE;
+  }
+
+  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
+    }
+    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
+      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
+    }
+
+    $old_schema = $this->introspectSchema($table);
+    $new_schema = $old_schema;
+
+    // Map the old field to the new field.
+    if ($field != $field_new) {
+      $mapping[$field_new] = $field;
+    }
+    else {
+      $mapping = array();
+    }
+
+    // Remove the previous definition and swap in the new one.
+    unset($new_schema['fields'][$field]);
+    $new_schema['fields'][$field_new] = $spec;
+
+    // Map the former indexes to the new column name.
+    $new_schema['primary key'] = $this->mapKeyDefinition($new_schema['primary key'], $mapping);
+    foreach (array('unique keys', 'indexes') as $k) {
+      foreach ($new_schema[$k] as &$key_definition) {
+        $key_definition = $this->mapKeyDefinition($key_definition, $mapping);
+      }
+    }
+
+    // Add in the keys from $keys_new.
+    if (isset($keys_new['primary key'])) {
+      $new_schema['primary key'] = $keys_new['primary key'];
+    }
+    foreach (array('unique keys', 'indexes') as $k) {
+      if (!empty($keys_new[$k])) {
+        $new_schema[$k] = $keys_new[$k] + $new_schema[$k];
+      }
+    }
+
+    $this->alterTable($table, $old_schema, $new_schema, $mapping);
+  }
+
+  /**
+   * Utility method: rename columns in an index definition according to a new mapping.
+   *
+   * @param $key_definition
+   *   The key definition.
+   * @param $mapping
+   *   The new mapping.
+   */
+  protected function mapKeyDefinition(array $key_definition, array $mapping) {
+    foreach ($key_definition as &$field) {
+      // The key definition can be an array($field, $length).
+      if (is_array($field)) {
+        $field = &$field[0];
+      }
+      if (isset($mapping[$field])) {
+        $field = $mapping[$field];
+      }
+    }
+    return $key_definition;
+  }
+
+  public function addIndex($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->indexExists($table, $name)) {
+      throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $schema['indexes'][$name] = $fields;
+    $statements = $this->createIndexSql($table, $schema);
+    foreach ($statements as $statement) {
+      $this->connection->query($statement);
+    }
+  }
+
+  public function indexExists($table, $name) {
+    $info = $this->getPrefixInfo($table);
+
+    return $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $info['table'] . '_' . $name . ')')->fetchField() != '';
+  }
+
+  public function dropIndex($table, $name) {
+    if (!$this->indexExists($table, $name)) {
+      return FALSE;
+    }
+
+    $info = $this->getPrefixInfo($table);
+
+    $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
+    return TRUE;
+  }
+
+  public function addUniqueKey($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->indexExists($table, $name)) {
+      throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $schema['unique keys'][$name] = $fields;
+    $statements = $this->createIndexSql($table, $schema);
+    foreach ($statements as $statement) {
+      $this->connection->query($statement);
+    }
+  }
+
+  public function dropUniqueKey($table, $name) {
+    if (!$this->indexExists($table, $name)) {
+      return FALSE;
+    }
+
+    $info = $this->getPrefixInfo($table);
+
+    $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
+    return TRUE;
+  }
+
+  public function addPrimaryKey($table, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
+    }
+
+    $old_schema = $this->introspectSchema($table);
+    $new_schema = $old_schema;
+
+    if (!empty($new_schema['primary key'])) {
+      throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
+    }
+
+    $new_schema['primary key'] = $fields;
+    $this->alterTable($table, $old_schema, $new_schema);
+  }
+
+  public function dropPrimaryKey($table) {
+    $old_schema = $this->introspectSchema($table);
+    $new_schema = $old_schema;
+
+    if (empty($new_schema['primary key'])) {
+      return FALSE;
+    }
+
+    unset($new_schema['primary key']);
+    $this->alterTable($table, $old_schema, $new_schema);
+    return TRUE;
+  }
+
+  public function fieldSetDefault($table, $field, $default) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+    }
+
+    $old_schema = $this->introspectSchema($table);
+    $new_schema = $old_schema;
+
+    $new_schema['fields'][$field]['default'] = $default;
+    $this->alterTable($table, $old_schema, $new_schema);
+  }
+
+  public function fieldSetNoDefault($table, $field) {
+    if (!$this->fieldExists($table, $field)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+    }
+
+    $old_schema = $this->introspectSchema($table);
+    $new_schema = $old_schema;
+
+    unset($new_schema['fields'][$field]['default']);
+    $this->alterTable($table, $old_schema, $new_schema);
+  }
+
+  public function findTables($table_expression) {
+    // Don't add the prefix, $table_expression already includes the prefix.
+    $info = $this->getPrefixInfo($table_expression, FALSE);
+
+    // Can't use query placeholders for the schema because the query would have
+    // to be :prefixsqlite_master, which does not work.
+    $result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
+      ':type' => 'table',
+      ':table_name' => $info['table'],
+    ));
+    return $result->fetchAllKeyed(0, 0);
+  }
+}
diff --git a/core/drivers/sqlite/src/Select.php b/core/drivers/sqlite/src/Select.php
new file mode 100644
index 0000000..a5a8020
--- /dev/null
+++ b/core/drivers/sqlite/src/Select.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Select
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Query\Select as QuerySelect;
+
+class Select extends QuerySelect {
+  public function forUpdate($set = TRUE) {
+    // SQLite does not support FOR UPDATE so nothing to do.
+    return $this;
+  }
+}
\ No newline at end of file
diff --git a/core/drivers/sqlite/src/Statement.php b/core/drivers/sqlite/src/Statement.php
new file mode 100644
index 0000000..8d7a725
--- /dev/null
+++ b/core/drivers/sqlite/src/Statement.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Statement
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\StatementPrefetch;
+use Drupal\Core\Database\StatementInterface;
+
+/**
+ * Specific SQLite implementation of DatabaseConnection.
+ *
+ * The PDO SQLite driver only closes SELECT statements when the PDOStatement
+ * destructor is called and SQLite does not allow data change (INSERT,
+ * UPDATE etc) on a table which has open SELECT statements. This is a
+ * user-space mock of PDOStatement that buffers all the data and doesn't
+ * have those limitations.
+ */
+class Statement extends StatementPrefetch implements \Iterator, StatementInterface {
+
+  /**
+   * SQLite specific implementation of getStatement().
+   *
+   * The PDO SQLite layer doesn't replace numeric placeholders in queries
+   * correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
+   * fail. We replace numeric placeholders in the query ourselves to work
+   * around this bug.
+   *
+   * See http://bugs.php.net/bug.php?id=45259 for more details.
+   */
+  protected function getStatement($query, &$args = array()) {
+    if (count($args)) {
+      // Check if $args is a simple numeric array.
+      if (range(0, count($args) - 1) === array_keys($args)) {
+        // In that case, we have unnamed placeholders.
+        $count = 0;
+        $new_args = array();
+        foreach ($args as $value) {
+          if (is_float($value) || is_int($value)) {
+            if (is_float($value)) {
+              // Force the conversion to float so as not to loose precision
+              // in the automatic cast.
+              $value = sprintf('%F', $value);
+            }
+            $query = substr_replace($query, $value, strpos($query, '?'), 1);
+          }
+          else {
+            $placeholder = ':db_statement_placeholder_' . $count++;
+            $query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
+            $new_args[$placeholder] = $value;
+          }
+        }
+        $args = $new_args;
+      }
+      else {
+        // Else, this is using named placeholders.
+        foreach ($args as $placeholder => $value) {
+          if (is_float($value) || is_int($value)) {
+            if (is_float($value)) {
+              // Force the conversion to float so as not to loose precision
+              // in the automatic cast.
+              $value = sprintf('%F', $value);
+            }
+
+            // We will remove this placeholder from the query as PDO throws an
+            // exception if the number of placeholders in the query and the
+            // arguments does not match.
+            unset($args[$placeholder]);
+            // PDO allows placeholders to not be prefixed by a colon. See
+            // http://marc.info/?l=php-internals&m=111234321827149&w=2 for
+            // more.
+            if ($placeholder[0] != ':') {
+              $placeholder = ":$placeholder";
+            }
+            // When replacing the placeholders, make sure we search for the
+            // exact placeholder. For example, if searching for
+            // ':db_placeholder_1', do not replace ':db_placeholder_11'.
+            $query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
+          }
+        }
+      }
+    }
+
+    return $this->dbh->prepare($query);
+  }
+
+  public function execute($args = array(), $options = array()) {
+    try {
+      $return = parent::execute($args, $options);
+    }
+    catch (\PDOException $e) {
+      if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
+        // The schema has changed. SQLite specifies that we must resend the query.
+        $return = parent::execute($args, $options);
+      }
+      else {
+        // Rethrow the exception.
+        throw $e;
+      }
+    }
+
+    // In some weird cases, SQLite will prefix some column names by the name
+    // of the table. We post-process the data, by renaming the column names
+    // using the same convention as MySQL and PostgreSQL.
+    $rename_columns = array();
+    foreach ($this->columnNames as $k => $column) {
+      // In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
+      // instead of "field".
+      if (preg_match("/^\((.*)\)$/", $column, $matches)) {
+        $rename_columns[$column] = $matches[1];
+        $this->columnNames[$k] = $matches[1];
+        $column = $matches[1];
+      }
+
+      // Remove "table." prefixes.
+      if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
+        $rename_columns[$column] = $matches[1];
+        $this->columnNames[$k] = $matches[1];
+      }
+    }
+    if ($rename_columns) {
+      // DatabaseStatementPrefetch already extracted the first row,
+      // put it back into the result set.
+      if (isset($this->currentRow)) {
+        $this->data[0] = &$this->currentRow;
+      }
+
+      // Then rename all the columns across the result set.
+      foreach ($this->data as $k => $row) {
+        foreach ($rename_columns as $old_column => $new_column) {
+          $this->data[$k][$new_column] = $this->data[$k][$old_column];
+          unset($this->data[$k][$old_column]);
+        }
+      }
+
+      // Finally, extract the first row again.
+      $this->currentRow = $this->data[0];
+      unset($this->data[0]);
+    }
+
+    return $return;
+  }
+}
diff --git a/core/drivers/sqlite/src/Transaction.php b/core/drivers/sqlite/src/Transaction.php
new file mode 100644
index 0000000..002f38c
--- /dev/null
+++ b/core/drivers/sqlite/src/Transaction.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Transaction
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Transaction as DatabaseTransaction;
+
+class Transaction extends DatabaseTransaction { }
diff --git a/core/drivers/sqlite/src/Truncate.php b/core/drivers/sqlite/src/Truncate.php
new file mode 100644
index 0000000..cf74e54
--- /dev/null
+++ b/core/drivers/sqlite/src/Truncate.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Truncate
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Query\Truncate as QueryTruncate;
+
+/**
+ * SQLite specific implementation of TruncateQuery.
+ *
+ * SQLite doesn't support TRUNCATE, but a DELETE query with no condition has
+ * exactly the effect (it is implemented by DROPing the table).
+ */
+class Truncate extends QueryTruncate {
+  public function __toString() {
+    // Create a sanitized comment string to prepend to the query.
+    $comments = $this->connection->makeComment($this->comments);
+
+    return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
+  }
+}
diff --git a/core/drivers/sqlite/src/Update.php b/core/drivers/sqlite/src/Update.php
new file mode 100644
index 0000000..47f99b5
--- /dev/null
+++ b/core/drivers/sqlite/src/Update.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\sqlite\Update
+ */
+
+namespace Drupal\sqlite;
+
+use Drupal\Core\Database\Query\Update as QueryUpdate;
+
+class Update extends QueryUpdate { }
diff --git a/core/includes/install.inc b/core/includes/install.inc
index bcca67c..ab0faa8 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -135,23 +135,15 @@ function drupal_detect_database_types() {
  */
 function drupal_get_database_types() {
   $databases = array();
-  $drivers = array();
+  $discovery = new ExtensionDiscovery();
+  $discovery->setProfileDirectories(array());
 
-  // The internal database driver name is any valid PHP identifier.
-  $mask = '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '$/';
-  $files = file_scan_directory(DRUPAL_ROOT . '/core/lib/Drupal/Core/Database/Driver', $mask, array('recurse' => FALSE));
-  if (is_dir(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database')) {
-    $files += file_scan_directory(DRUPAL_ROOT . '/drivers/lib/Drupal/Driver/Database/', $mask, array('recurse' => FALSE));
-  }
-  foreach ($files as $file) {
-    if (file_exists($file->uri . '/Install/Tasks.php')) {
-      $drivers[$file->filename] = $file->uri;
-    }
-  }
-  foreach ($drivers as $driver => $file) {
-    $installer = db_installer_object($driver);
+  foreach ($discovery->scan('driver') as $driver_name => $driver) {
+    drupal_classloader_register($driver_name, $driver->getPath());
+
+    $installer = db_installer_object($driver_name);
     if ($installer->installable()) {
-      $databases[$driver] = $installer;
+      $databases[$driver_name] = $installer;
     }
   }
 
@@ -1127,12 +1119,6 @@ function db_run_tasks($driver) {
 function db_installer_object($driver) {
   // We cannot use Database::getConnection->getDriverClass() here, because
   // the connection object is not yet functional.
-  $task_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Install\\Tasks";
-  if (class_exists($task_class)) {
-    return new $task_class();
-  }
-  else {
-    $task_class = "Drupal\\Driver\\Database\\{$driver}\\Install\\Tasks";
-    return new $task_class();
-  }
+  $task_class = "Drupal\\{$driver}\\Install\\Tasks";
+  return new $task_class();
 }
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index e0c1714..14df9d7 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -635,13 +635,7 @@ protected function expandArguments(&$query, &$args) {
   public function getDriverClass($class) {
     if (empty($this->driverClasses[$class])) {
       $driver = $this->driver();
-      if (!empty($this->connectionOptions['namespace'])) {
-        $driver_class  = $this->connectionOptions['namespace'] . '\\' . $class;
-      }
-      else {
-        // Fallback for Drupal 7 settings.php.
-        $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\{$class}";
-      }
+      $driver_class = "Drupal\\{$driver}\\{$class}";
       $this->driverClasses[$class] = class_exists($driver_class) ? $driver_class : $class;
     }
     return $this->driverClasses[$class];
diff --git a/core/lib/Drupal/Core/Database/Database.php b/core/lib/Drupal/Core/Database/Database.php
index a799926..7113254 100644
--- a/core/lib/Drupal/Core/Database/Database.php
+++ b/core/lib/Drupal/Core/Database/Database.php
@@ -255,7 +255,13 @@
    */
   final public static function addConnectionInfo($key, $target, array $info) {
     if (empty(self::$databaseInfo[$key][$target])) {
-      self::$databaseInfo[$key][$target] = self::parseConnectionInfo($info);
+      $info = self::parseConnectionInfo($info);
+      self::$databaseInfo[$key][$target] = $info;
+
+      // @todo Hard-wired dependency on Composer's ClassLoader.
+      \ComposerAutoloaderInitDrupal8::getLoader()
+        // Ensure to set (replace), not add (cumulative) for each connection.
+        ->setPsr4('Drupal\\' . $info['driver'] . '\\', $info['path']);
     }
   }
 
@@ -370,13 +376,7 @@
       throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
     }
 
-    if (!empty(self::$databaseInfo[$key][$target]['namespace'])) {
-      $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection';
-    }
-    else {
-      // Fallback for Drupal 7 settings.php.
-      $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
-    }
+    $driver_class = "Drupal\\$driver\\Connection";
 
     $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
     $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/ConditionResolver.php b/core/lib/Drupal/Core/Database/Driver/fake/ConditionResolver.php
deleted file mode 100644
index ea69504..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/ConditionResolver.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\ConditionResolver.
- */
-
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\Query\Condition;
-
-class ConditionResolver {
-
-  /**
-   * Match a row against a group of conditions.
-   *
-   * @param \Drupal\Core\Database\Driver\fake\DatabaseRowInterface $row
-   *   The database row object.
-   * @param \Drupal\Core\Database\Query\Condition $condition_group
-   *   The condition group object.
-   *
-   * @return bool
-   *   TRUE if there is a match.
-   */
-  public static function matchGroup(DatabaseRowInterface $row, Condition $condition_group) {
-    $conditions = $condition_group->conditions();
-    $and = $conditions['#conjunction'] == 'AND';
-    unset($conditions['#conjunction']);
-    $match = TRUE;
-    foreach ($conditions as $condition) {
-      $match = $condition['field'] instanceof Condition ? static::matchGroup($row, $condition['field']) : static::matchSingle($row, $condition);
-      // For AND, finish matching on the first fail. For OR, finish on first
-      // success.
-      if ($and != $match) {
-        break;
-      }
-    }
-    return $match;
-  }
-
-  /**
-   * Match a single row and its condition.
-   *
-   * @param \Drupal\migrate\tests\DatabaseRowInterface $row
-   *   The row to match.
-   *
-   * @param array $condition
-   *   An array representing a single condition.
-   *
-   * @return bool
-   *   TRUE if the condition matches.
-   *
-   * @throws \Exception
-   */
-  protected static function matchSingle(DatabaseRowInterface $row, array $condition) {
-    $row_value = $row->getValue($condition['field']);
-    switch ($condition['operator']) {
-      case '=':
-        return $row_value == $condition['value'];
-
-      case '<=':
-        return $row_value <= $condition['value'];
-
-      case '>=':
-        return $row_value >= $condition['value'];
-
-      case '!=':
-        return $row_value != $condition['value'];
-
-      case '<>':
-        return $row_value != $condition['value'];
-
-      case '<':
-        return $row_value < $condition['value'];
-
-      case '>':
-        return $row_value > $condition['value'];
-
-      case 'IN':
-        return in_array($row_value, $condition['value']);
-
-      case 'IS NULL':
-        return !isset($row_value);
-
-      case 'IS NOT NULL':
-        return isset($row_value);
-
-      default:
-        throw new \Exception(sprintf('operator %s is not supported', $condition['operator']));
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRow.php b/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRow.php
deleted file mode 100644
index ca98582..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRow.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\DatabaseRow.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-class DatabaseRow implements DatabaseRowInterface {
-
-  /**
-   * Construct a new row.
-   *
-   * @param array $row
-   *   The row data.
-   */
-  public function __construct(array $row) {
-    $this->row = $row;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getValue($field) {
-    return $this->row[$field];
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRowInterface.php b/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRowInterface.php
deleted file mode 100644
index 9aa3965..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRowInterface.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\DatabaseRowInterface.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-interface DatabaseRowInterface {
-
-  /**
-   * Get the field value from the row.
-   *
-   * @param mixed $field
-   *   The field to get the value of.
-   *
-   * @return mixed
-   *   The field value from the row.
-   */
-  public function getValue($field);
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRowSelect.php b/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRowSelect.php
deleted file mode 100644
index 41bbf55..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/DatabaseRowSelect.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\DatabaseRowSelect.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-class DatabaseRowSelect extends DatabaseRow {
-
-  /**
-   * Construct a new database row.
-   *
-   * @param array $row
-   *   The row data.
-   * @param array $fields_with_table
-   *   The fields with a table.
-   * @param array $fields
-   *   The fields.
-   */
-  public function __construct(array $row, array $fields_with_table, array $fields) {
-    $this->fieldsWithTable = $fields_with_table;
-    $this->fields = $fields;
-    parent::__construct($row);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getValue($field) {
-    $field_info = isset($this->fieldsWithTable[$field]) ? $this->fieldsWithTable[$field] : $this->fields[$field];
-    if (array_key_exists($field_info['field'], $this->row[$field_info['table']]['result'])) {
-      $index = 'result';
-    }
-    else {
-      $index = 'all';
-    }
-    return $this->row[$field_info['table']][$index][$field_info['field']];
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeConnection.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeConnection.php
deleted file mode 100644
index 5738ab3..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeConnection.php
+++ /dev/null
@@ -1,160 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeConnection.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\Connection;
-
-/**
- * Defines a fake connection for use during testing.
- */
-class FakeConnection extends Connection {
-
-  /**
-   * Table prefix on the database.
-   *
-   * @var string
-   */
-  protected $tablePrefix;
-
-  /**
-   * Connection options for the database.
-   *
-   * @var array
-   */
-  protected $connectionOptions;
-
-  /**
-   * Constructs a FakeConnection.
-   *
-   * @param array $database_contents
-   *   The database contents faked as an array. Each key is a table name, each
-   *   value is a list of table rows.
-   * @param array $connection_options
-   *   (optional) The array of connection options for the database.
-   * @param string $prefix
-   *   (optional) The table prefix on the database.
-   */
-  public function __construct(array $database_contents, $connection_options = array(), $prefix = '') {
-    $this->databaseContents = $database_contents;
-    $this->connectionOptions = $connection_options;
-    $this->tablePrefix = $prefix;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConnectionOptions() {
-    return $this->connectionOptions;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function tablePrefix($table = 'default') {
-    return $this->tablePrefix;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function select($table, $alias = NULL, array $options = array()) {
-    return new FakeSelect($this->databaseContents, $table, $alias);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function insert($table, array $options = array()) {
-    return new FakeInsert($this->databaseContents, $table);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function merge($table, array $options = array()) {
-    return new FakeMerge($this->databaseContents, $table);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function update($table, array $options = array()) {
-    return new FakeUpdate($this->databaseContents, $table);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function truncate($table, array $options = array()) {
-    return new FakeTruncate($this->databaseContents, $table);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function schema() {
-    return new FakeDatabaseSchema($this->databaseContents);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query($query, array $args = array(), $options = array()) {
-    throw new \Exception('Method not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
-    throw new \Exception('Method not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function queryTemporary($query, array $args = array(), array $options = array()) {
-    throw new \Exception('Method not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function driver() {
-    throw new \Exception('Method not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function databaseType() {
-    throw new \Exception('Method not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function createDatabase($database) {
-    // There is nothing to do.
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function mapConditionOperator($operator) {
-    throw new \Exception('Method not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function nextId($existing_id = 0) {
-    throw new \Exception('Method not supported');
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeDatabaseSchema.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeDatabaseSchema.php
deleted file mode 100644
index 20e9da6..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeDatabaseSchema.php
+++ /dev/null
@@ -1,248 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeDatabaseSchema.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\Schema;
-
-class FakeDatabaseSchema extends Schema {
-
-  /**
-   * As set on MigrateSqlSourceTestCase::databaseContents.
-   *
-   * @var array
-   */
-  public $databaseContents;
-
-  /**
-   * Constructs a fake database schema.
-   *
-   * @param array $database_contents
-   *   The database contents faked as an array. Each key is a table name, each
-   *   value is a list of table rows.
-   */
-  public function __construct(array &$database_contents) {
-    $this->uniqueIdentifier = uniqid('', TRUE);
-
-    // @todo Maybe we can generate an internal representation.
-    $this->databaseContents = &$database_contents;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function tableExists($table) {
-    return in_array($table, array_keys($this->databaseContents));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prefixNonTable($table) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addField($table, $field, $spec, $keys_new = array()) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addIndex($table, $name, $fields) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addPrimaryKey($table, $fields) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addUniqueKey($table, $name, $fields) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * PHP magic __clone() method.
-   */
-  public function __clone() {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function copyTable($source, $destination) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function createTable($name, $table) {
-    #throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function dropField($table, $field) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function dropIndex($table, $name) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function dropPrimaryKey($table) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function dropTable($table) {
-    unset($this->databaseContents[$table]);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function dropUniqueKey($table, $name) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fieldExists($table, $column) {
-    if (!empty($this->databaseContents[$table])) {
-      $row = reset($this->databaseContents[$table]);
-      return isset($row[$column]);
-    }
-    else {
-      throw new \Exception("Can't determine whether field exists with an empty / nonexistent table.");
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fieldNames($fields) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fieldSetDefault($table, $field, $default) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fieldSetNoDefault($table, $field) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function findTables($table_expression) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFieldTypeMap() {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function indexExists($table, $name) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function nextPlaceholder() {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareComment($comment, $length = NULL) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function renameTable($table, $new_name) {
-    throw new \Exception(sprintf('Unsupported method "%s"', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function uniqueIdentifier() {
-    return $this->uniqueIdentifier;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Fake database schema',
-      'description' => 'Tests for fake database schema plugin.',
-      'group' => 'Migrate',
-    );
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeInsert.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeInsert.php
deleted file mode 100644
index 9d7012b..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeInsert.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeInsert.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\Query\Insert;
-use Drupal\Core\Database\Query\SelectInterface;
-
-/**
- * Defines FakeInsert for use in database tests.
- */
-class FakeInsert extends Insert {
-
-  /**
-   * The database contents.
-   *
-   * @var array
-   */
-  protected $databaseContents;
-
-  /**
-   * The database table to insert into.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
-   * Constructs a fake insert object.
-   *
-   * @param array $database_contents
-   *   The database contents faked as an array. Each key is a table name, each
-   *   value is a list of table rows.
-   * @param string $table
-   *   The table to insert into.
-   * @param array $options
-   *   (optional) The database options. Not used.
-   */
-  public function __construct(array &$database_contents, $table, array $options = array()) {
-    $this->databaseContents = &$database_contents;
-    $this->table = $table;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function useDefaults(array $fields) {
-    throw new \Exception('This method is not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function from(SelectInterface $query) {
-    throw new \Exception('This method is not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    foreach ($this->insertValues as $values) {
-      $this->databaseContents[$this->table][] = array_combine($this->insertFields, $values);
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeMerge.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeMerge.php
deleted file mode 100644
index dd3117e..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeMerge.php
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeMerge.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Database\Query\InvalidMergeQueryException;
-use Drupal\Core\Database\Query\Merge;
-
-/**
- * Defines FakeMerge for use in database tests.
- */
-class FakeMerge extends Merge {
-
-  /**
-   * Constructs a fake merge object and initializes the conditions.
-   *
-   * @param array $database_contents
-   *   The database contents faked as an array. Each key is a table name, each
-   *   value is a list of table rows.
-   * @param string $table
-   *   The database table to merge into.
-   */
-  public function __construct(array &$database_contents, $table) {
-    $this->databaseContents = &$database_contents;
-    $this->table = $table;
-    $this->conditionTable = $table;
-    $this->condition = new Condition('AND');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    if (!count($this->condition)) {
-      throw new InvalidMergeQueryException(t('Invalid merge query: no conditions'));
-    }
-    $select = new FakeSelect($this->databaseContents, $this->conditionTable, 'c');
-    $count = $select
-      ->condition($this->condition)
-      ->countQuery()
-      ->execute()
-      ->fetchField();
-    if ($count) {
-      $update = new FakeUpdate($this->databaseContents, $this->table);
-      $update
-        ->fields($this->updateFields)
-        ->condition($this->condition)
-        ->execute();
-      return self::STATUS_UPDATE;
-    }
-    $insert = new FakeInsert($this->databaseContents, $this->table);
-    $insert->fields($this->insertFields)->execute();
-    return self::STATUS_INSERT;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeSelect.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeSelect.php
deleted file mode 100644
index 629bf1a..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeSelect.php
+++ /dev/null
@@ -1,540 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeSelect.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Component\Utility\String;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Database\Query\PlaceholderInterface;
-use Drupal\Core\Database\Query\Select;
-use Drupal\Core\Database\Query\SelectInterface;
-
-class FakeSelect extends Select {
-
-  /**
-   * Contents of the pseudo-database.
-   *
-   * Keys are table names and values are arrays of rows in the table.
-   * Every row there contains all table field values keyed by field name.
-   *
-   * @code
-   * array(
-   *   'user' => array(
-   *     array(
-   *       'uid' => 1,
-   *       'name' => 'admin',
-   *     ),
-   *     array(
-   *       'uid' => 2,
-   *       'name' => 'alice',
-   *     ),
-   *   ),
-   *   'node' => array(
-   *     array(
-   *       'nid' => 1,
-   *     )
-   *   )
-   * )
-   * @endcode
-   *
-   * @var array
-   */
-  protected $databaseContents;
-
-  protected $countQuery = FALSE;
-  protected $fieldsWithTable = array();
-
-  /**
-   * Constructs a new FakeSelect.
-   *
-   * @param array $database_contents
-   *   An array of mocked database content.
-   * @param string $table
-   *   The base table name used within fake select.
-   * @param string $alias
-   *   The base table alias used within fake select.
-   * @param string $conjunction
-   *   The operator to use to combine conditions: 'AND' or 'OR'.
-   */
-  public function __construct(array $database_contents, $table, $alias, $conjunction = 'AND') {
-    $this->databaseContents = $database_contents;
-    $this->addJoin(NULL, $table, $alias);
-    $this->where = new Condition($conjunction);
-    $this->having = new Condition($conjunction);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
-    return $this->addJoin('LEFT', $table, $alias, $condition, $arguments);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) {
-    if ($table instanceof SelectInterface) {
-      // @todo implement this.
-      throw new \Exception('Subqueries are not supported at this moment.');
-    }
-    $alias = parent::addJoin($type, $table, $alias, $condition, $arguments);
-    if (isset($type)) {
-      if ($type != 'INNER' && $type != 'LEFT') {
-        throw new \Exception(sprintf('%s type not supported, only INNER and LEFT.', $type));
-      }
-      if (!preg_match('/^(\w+\.)?(\w+)\s*=\s*(\w+)\.(\w+)$/', $condition, $matches)) {
-        throw new \Exception('Only x.field1 = y.field2 conditions are supported.' . $condition);
-      }
-      if (!$matches[1] && count($this->tables) == 2) {
-        $aliases = array_keys($this->tables);
-        $matches[1] = $aliases[0];
-      }
-      else {
-        $matches[1] = substr($matches[1], 0, -1);
-      }
-      if (!$matches[1]) {
-        throw new \Exception('Only x.field1 = y.field2 conditions are supported.' . $condition);
-      }
-      if ($matches[1] == $alias) {
-        $this->tables[$alias] += array(
-          'added_field' => $matches[2],
-          'original_table_alias' => $matches[3],
-          'original_field' => $matches[4],
-        );
-      }
-      elseif ($matches[3] == $alias) {
-        $this->tables[$alias] += array(
-          'added_field' => $matches[4],
-          'original_table_alias' => $matches[1],
-          'original_field' => $matches[2],
-        );
-      }
-      else {
-        throw new \Exception('The JOIN condition does not contain the alias of the joined table.');
-      }
-    }
-    return $alias;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    // @todo: Implement distinct() handling.
-
-    // Single table count queries often do not contain fields which this class
-    // does not support otherwise, so add a shortcut.
-    if (count($this->tables) == 1 && $this->countQuery) {
-      $table_info = reset($this->tables);
-      $where = $this->where;
-      if (!empty($this->databaseContents[$table_info['table']])) {
-        $results = array_filter($this->databaseContents[$table_info['table']], function ($row_array) use ($where) {
-          return ConditionResolver::matchGroup(new DatabaseRow($row_array), $where);
-        });
-      }
-      else {
-        $results = array();
-      }
-    }
-    else {
-      $all_rows = $this->executeJoins();
-      $all_rows = $this->resolveConditions($this->where, $all_rows);
-      if (!empty($this->order)) {
-        usort($all_rows, array($this, 'sortCallback'));
-      }
-      // Now flatten the rows so that each row becomes a field alias => value
-      // array.
-      $results = array();
-      foreach ($all_rows as $table_rows) {
-        $result_row = array();
-        foreach ($table_rows as $row) {
-          $result_row += $row['result'];
-        }
-        $results[] = $result_row;
-      }
-    }
-    if (!empty($this->range)) {
-      $results = array_slice($results, $this->range['start'], $this->range['length']);
-    }
-    if ($this->countQuery) {
-      $results = array(array(count($results)));
-    }
-    return new FakeStatement($results);
-  }
-
-  /**
-   * Create an initial result set by executing the joins and picking fields.
-   *
-   * @return array
-   *   A multidimensional array, the first key are table aliases, the second
-   *   are field aliases, the values are the database contents or NULL in case
-   *   of JOINs.
-   */
-  protected function executeJoins() {
-    $fields = array();
-    foreach ($this->fields as $field_info) {
-      $this->fieldsWithTable[$field_info['table'] . '.' . $field_info['field']] = $field_info;
-      $fields[$field_info['table']][$field_info['field']] = NULL;
-    }
-    foreach ($this->tables as $alias => $table_info) {
-      if ($table = reset($this->databaseContents[$table_info['table']])) {
-        foreach (array_keys($table) as $field) {
-          if (!isset($this->fields[$field])) {
-            $this->fieldsWithTable[$field] = array(
-              'table' => $alias,
-              'field' => $field,
-            );
-            $this->fieldsWithTable["$alias.$field"] = array(
-              'table' => $alias,
-              'field' => $field,
-            );
-          }
-        }
-      }
-    }
-    // This will contain a multiple dimensional array. The first key will be a
-    // table alias, the second either result or all, the third will be a field
-    // alias. all contains every field in the table with the original field
-    // names while result contains only the fields requested. This allows for
-    // filtering on fields that were not added via addField().
-    $results = array();
-    foreach ($this->tables as $table_alias => $table_info) {
-      // The base table for this query.
-      if (empty($table_info['join type'])) {
-        foreach ($this->databaseContents[$table_info['table']] as $candidate_row) {
-          $results[] = $this->getNewRow($table_alias, $fields, $candidate_row);
-        }
-      }
-      else {
-        $new_rows = array();
-
-        // Dynamically build a set of joined rows. Check existing rows and see
-        // if they can be joined with incoming rows.
-        foreach ($results as $row) {
-          $joined = FALSE;
-          foreach ($this->databaseContents[$table_info['table']] as $candidate_row) {
-            if ($row[$table_info['original_table_alias']]['result'][$table_info['original_field']] == $candidate_row[$table_info['added_field']]) {
-              $joined = TRUE;
-              $new_rows[] = $this->getNewRow($table_alias, $fields, $candidate_row, $row);
-            }
-          }
-          if (!$joined && $table_info['join type'] == 'LEFT') {
-            // Because PHP doesn't scope their foreach statements,
-            // $candidate_row may contain the last value assigned to it from the
-            // previous statement.
-            // @TODO: empty tables? Those are a problem.
-            $keys = array_keys($candidate_row);
-            $values = array_fill(0, count($keys), NULL);
-            $new_row = array(
-              'result' => $fields[$table_alias],
-              'all' => array_combine($keys, $values),
-            );
-            $new_rows[] = array($table_alias => $new_row) + $row;
-          }
-        }
-        $results = $new_rows;
-      }
-    }
-    return $results;
-  }
-
-  /**
-   * Retrieves a new row.
-   *
-   * @param string $table_alias
-   * @param array $fields
-   * @param array $candidate_row
-   * @param array $row
-   *
-   * @return array
-   */
-  protected function getNewRow($table_alias, $fields, $candidate_row, $row = array()) {
-    $new_row[$table_alias]['all'] = $candidate_row;
-    foreach ($fields[$table_alias] as $field => $v) {
-      $new_row[$table_alias]['result'][$field] = $candidate_row[$field];
-    }
-    return $new_row + $row;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function countQuery() {
-    $query = clone $this;
-    return $query->setCountQuery();
-  }
-
-  /**
-   * Set this query to be a count query.
-   */
-  protected function setCountQuery() {
-    $this->countQuery = TRUE;
-    return $this;
-  }
-
-  /**
-   * usort callback to order the results.
-   */
-  protected function sortCallback($a, $b) {
-    $a_row = new DatabaseRowSelect($a, $this->fieldsWithTable, $this->fields);
-    $b_row = new DatabaseRowSelect($b, $this->fieldsWithTable, $this->fields);
-    foreach ($this->order as $field => $direction) {
-      $a_value = $a_row->getValue($field);
-      $b_value = $b_row->getValue($field);
-      if ($a_value != $b_value) {
-        return (($a_value < $b_value) == ($direction == 'ASC')) ? -1 : 1;
-      }
-    }
-    return 0;
-  }
-
-  /**
-   * Resolves conditions by removing non-matching rows.
-   *
-   * @param \Drupal\Core\Database\Query\Condition $condition_group
-   *   The condition group to check.
-   * @param array $rows
-   *   An array of rows excluding non-matching rows.
-   *
-   * @return \Drupal\Core\Database\Driver\fake\ConditionResolver
-   *   The condition resolver object.
-   */
-  protected function resolveConditions(Condition $condition_group, array &$rows) {
-    $fields_with_table = $this->fieldsWithTable;
-    $fields = $this->fields;
-    return array_filter($rows, function ($row_array) use ($condition_group, $fields_with_table, $fields) {
-      $row = new DatabaseRowSelect($row_array, $fields_with_table, $fields);
-      return ConditionResolver::matchGroup($row, $condition_group);
-    });
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function orderBy($field, $direction = 'ASC') {
-    $this->order[$field] = strtoupper($direction);
-    return $this;
-  }
-
-  // ================== we could support these.
-  /**
-   * {@inheritdoc}
-   */
-  public function groupBy($field) {
-    // @todo: Implement groupBy() method.
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function havingCondition($field, $value = NULL, $operator = NULL) {
-    // @todo: Implement havingCondition() method.
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function uniqueIdentifier() {
-    // TODO: Implement uniqueIdentifier() method.
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  // ================== the rest won't be supported, ever.
-  /**
-   * {@inheritdoc}
-   */
-  public function nextPlaceholder() {
-    // TODO: Implement nextPlaceholder() method.
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function isPrepared() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preExecute(SelectInterface $query = NULL) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function where($snippet, $args = array()) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function extend($extender_name) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &getExpressions() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &getGroupBy() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &getUnion() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function forUpdate($set = TRUE) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &conditions() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function orderRandom() {
-    // We could implement this but why bother.
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function union(SelectInterface $query, $type = '') {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function addExpression($expression, $alias = NULL, $arguments = array()) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &getTables() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getArguments(PlaceholderInterface $query_place_holder = NULL) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &getOrderBy() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function &getFields() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function exists(SelectInterface $select) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function notExists(SelectInterface $select) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function arguments() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function compile(Connection $connection, PlaceholderInterface $query_place_holder) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function compiled() {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Fake select test',
-      'description' => 'Tests for fake select plugin.',
-      'group' => 'Migrate',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields($table_alias, array $fields = array()) {
-    if (!$fields) {
-      $table = $this->tables[$table_alias]['table'];
-      if (!empty($this->databaseContents[$table])) {
-        $fields = array_keys(reset($this->databaseContents[$table]));
-      }
-      else {
-        throw new \Exception(String::format('All fields on empty table @table is not supported.', array('@table' => $table)));
-      }
-    }
-    return parent::fields($table_alias, $fields);
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeStatement.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeStatement.php
deleted file mode 100644
index 4f00eeb..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeStatement.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeStatement.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\RowCountException;
-use Drupal\Core\Database\StatementInterface;
-
-/**
- * Represents a fake prepared statement.
- */
-class FakeStatement extends \ArrayIterator implements StatementInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute($args = array(), $options = array()) {
-    throw new \Exception('This method is not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getQueryString() {
-    throw new \Exception('This method is not supported');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function rowCount() {
-    throw new RowCountException();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fetchField($index = 0) {
-    $row = array_values($this->current());
-    $return = $row[$index];
-    $this->next();
-    return $return;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fetchAssoc() {
-    $return = FALSE;
-    if ($this->valid()) {
-      $return = $this->current();
-      $this->next();
-    }
-    return $return;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fetchCol($index = 0) {
-    $return = array();
-    foreach ($this as $row) {
-      $row = array_values($row);
-      $return[] = $row[$index];
-    }
-    return $return;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fetchAllKeyed($key_index = 0, $value_index = 1) {
-    $return = array();
-    foreach ($this as $row) {
-      $row = array_values($row);
-      $return[$row[$key_index]] = $row[$value_index];
-    }
-    return $return;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fetchAllAssoc($key, $fetch = NULL) {
-    $return = array();
-    foreach ($this as $row) {
-      $return[$row[$key]] = $row;
-    }
-    return $return;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fetchObject() {
-    $return = $this->fetchAssoc();
-    return $return === FALSE ? FALSE : (object) $return;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function getInfo() {
-    return array(
-      'name' => 'Fake statement test',
-      'description' => 'Tests for fake statement plugin.',
-      'group' => 'Migrate',
-    );
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeTruncate.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeTruncate.php
deleted file mode 100644
index d0be873..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeTruncate.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeTruncate.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-/**
- * Defines FakeTruncate for use in database tests.
- */
-class FakeTruncate {
-
-  /**
-   * Constructs a FakeTruncate object.
-   *
-   * @param array $database_contents
-   *   The database contents faked as an array. Each key is a table name, each
-   *   value is a list of table rows.
-   * @param string $table
-   *   The table to truncate.
-   */
-  public function __construct(array &$database_contents, $table) {
-    $this->databaseContents = &$database_contents;
-    $this->table = $table;
-  }
-
-  /**
-   * Executes the TRUNCATE query.
-   */
-  public function execute() {
-    $this->databaseContents[$this->table] = array();
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/FakeUpdate.php b/core/lib/Drupal/Core/Database/Driver/fake/FakeUpdate.php
deleted file mode 100644
index 0505c22..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/FakeUpdate.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Database\Driver\fake\FakeUpdate.
- */
-
-namespace Drupal\Core\Database\Driver\fake;
-
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Database\Query\SelectInterface;
-use Drupal\Core\Database\Query\Update;
-
-/**
- * Defines FakeUpdate for use in database tests.
- */
-class FakeUpdate extends Update {
-
-  /**
-   * The database table to update.
-   *
-   * @var string
-   */
-  protected $table;
-
-  /**
-   * The database contents.
-   *
-   * @var array
-   */
-  protected $databaseContents;
-
-  /**
-   * Constructs a FakeUpdate object and initializes the condition.
-   *
-   * @param array $database_contents
-   *   The database contents faked as an array. Each key is a table name, each
-   *   value is a list of table rows.
-   * @param string $table
-   *   The table to update.
-   */
-  public function __construct(array &$database_contents, $table) {
-    $this->databaseContents = &$database_contents;
-    $this->table = $table;
-    $this->condition = new Condition('AND');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    $affected = 0;
-    if (isset($this->databaseContents[$this->table])) {
-      $fields = $this->fields;
-      $condition = $this->condition;
-      array_walk($this->databaseContents[$this->table], function (&$row_array) use ($fields, $condition, &$affected) {
-        $row = new DatabaseRow($row_array);
-        if (ConditionResolver::matchGroup($row, $condition)) {
-          $row_array = $fields + $row_array;
-          $affected++;
-        }
-      });
-    }
-    return $affected;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function exists(SelectInterface $select) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function where($snippet, $args = array()) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function expression($field, $expression, array $arguments = NULL) {
-    throw new \Exception(sprintf('Method "%s" is not supported', __METHOD__));
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/fake/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/fake/Install/Tasks.php
deleted file mode 100644
index 4ac7487..0000000
--- a/core/lib/Drupal/Core/Database/Driver/fake/Install/Tasks.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\fake\Install\Tasks
- */
-
-namespace Drupal\Core\Database\Driver\fake\Install;
-
-use Drupal\Core\Database\Install\Tasks as InstallTasks;
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Driver\mysql\Connection;
-use Drupal\Core\Database\DatabaseNotFoundException;
-
-/**
- * Usually used to specify installation tasks but here we're only interested
- * in setting $error to TRUE.
- */
-class Tasks extends InstallTasks {
-  /**
-   * Prevent the installer from recognising this as a potential database driver.
-   * @TODO Looks like this is needed only if we define $pdoDriver to something valid e.g. mysql.
-   * Not sure we need to do that. We may not need this file at all?
-   *
-   * @var boolean
-   */
-  protected $error = TRUE;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function name() {
-    return t('Fake database connection for use in unit tests');
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
deleted file mode 100644
index 66cd34f..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
+++ /dev/null
@@ -1,275 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Connection
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\DatabaseExceptionWrapper;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\DatabaseNotFoundException;
-use Drupal\Core\Database\TransactionCommitFailedException;
-use Drupal\Core\Database\DatabaseException;
-use Drupal\Core\Database\Connection as DatabaseConnection;
-
-/**
- * @addtogroup database
- * @{
- */
-
-class Connection extends DatabaseConnection {
-
-  /**
-   * Error code for "Unknown database" error.
-   */
-  const DATABASE_NOT_FOUND = 1049;
-
-  /**
-   * Flag to indicate if the cleanup function in __destruct() should run.
-   *
-   * @var boolean
-   */
-  protected $needsCleanup = FALSE;
-
-  /**
-   * Constructs a Connection object.
-   */
-  public function __construct(\PDO $connection, array $connection_options = array()) {
-    parent::__construct($connection, $connection_options);
-
-    // This driver defaults to transaction support, except if explicitly passed FALSE.
-    $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
-
-    // MySQL never supports transactional DDL.
-    $this->transactionalDDLSupport = FALSE;
-
-    $this->connectionOptions = $connection_options;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function open(array &$connection_options = array()) {
-    // The DSN should use either a socket or a host/port.
-    if (isset($connection_options['unix_socket'])) {
-      $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
-    }
-    else {
-      // Default to TCP connection on port 3306.
-      $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
-    }
-    if (!empty($connection_options['database'])) {
-      $dsn .= ';dbname=' . $connection_options['database'];
-    }
-    // Allow PDO options to be overridden.
-    $connection_options += array(
-      'pdo' => array(),
-    );
-    $connection_options['pdo'] += array(
-      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
-      // So we don't have to mess around with cursors and unbuffered queries by default.
-      \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
-      // Make sure MySQL returns all matched rows on update queries including
-      // rows that actually didn't have to be updated because the values didn't
-      // change. This matches common behavior among other database systems.
-      \PDO::MYSQL_ATTR_FOUND_ROWS => TRUE,
-      // Because MySQL's prepared statements skip the query cache, because it's dumb.
-      \PDO::ATTR_EMULATE_PREPARES => TRUE,
-    );
-
-    $pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
-
-    // Force MySQL to use the UTF-8 character set. Also set the collation, if a
-    // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
-    // for UTF-8.
-    if (!empty($connection_options['collation'])) {
-      $pdo->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']);
-    }
-    else {
-      $pdo->exec('SET NAMES utf8');
-    }
-
-    // Set MySQL init_commands if not already defined.  Default Drupal's MySQL
-    // behavior to conform more closely to SQL standards.  This allows Drupal
-    // to run almost seamlessly on many different kinds of database systems.
-    // These settings force MySQL to behave the same as postgresql, or sqlite
-    // in regards to syntax interpretation and invalid data handling.  See
-    // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
-    // changed the meaning of TRADITIONAL we need to spell out the modes one by
-    // one.
-    $connection_options += array(
-      'init_commands' => array(),
-    );
-    $connection_options['init_commands'] += array(
-      'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
-    );
-    // Set connection options.
-    $pdo->exec(implode('; ', $connection_options['init_commands']));
-
-    return $pdo;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function serialize() {
-    // Cleanup the connection, much like __destruct() does it as well.
-    if ($this->needsCleanup) {
-      $this->nextIdDelete();
-    }
-    $this->needsCleanup = FALSE;
-
-    return parent::serialize();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function __destruct() {
-    if ($this->needsCleanup) {
-      $this->nextIdDelete();
-    }
-  }
-
-  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
-    return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
-  }
-
-  public function queryTemporary($query, array $args = array(), array $options = array()) {
-    $tablename = $this->generateTemporaryTableName();
-    $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
-    return $tablename;
-  }
-
-  public function driver() {
-    return 'mysql';
-  }
-
-  public function databaseType() {
-    return 'mysql';
-  }
-
-  /**
-   * Overrides \Drupal\Core\Database\Connection::createDatabase().
-   *
-   * @param string $database
-   *   The name of the database to create.
-   *
-   * @throws \Drupal\Core\Database\DatabaseNotFoundException
-   */
-  public function createDatabase($database) {
-    // Escape the database name.
-    $database = Database::getConnection()->escapeDatabase($database);
-
-    try {
-      // Create the database and set it as active.
-      $this->connection->exec("CREATE DATABASE $database");
-      $this->connection->exec("USE $database");
-    }
-    catch (\Exception $e) {
-      throw new DatabaseNotFoundException($e->getMessage());
-    }
-  }
-
-  public function mapConditionOperator($operator) {
-    // We don't want to override any of the defaults.
-    return NULL;
-  }
-
-  public function nextId($existing_id = 0) {
-    $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
-    // This should only happen after an import or similar event.
-    if ($existing_id >= $new_id) {
-      // If we INSERT a value manually into the sequences table, on the next
-      // INSERT, MySQL will generate a larger value. However, there is no way
-      // of knowing whether this value already exists in the table. MySQL
-      // provides an INSERT IGNORE which would work, but that can mask problems
-      // other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY
-      // UPDATE in such a way that the UPDATE does not do anything. This way,
-      // duplicate keys do not generate errors but everything else does.
-      $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id));
-      $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
-    }
-    $this->needsCleanup = TRUE;
-    return $new_id;
-  }
-
-  public function nextIdDelete() {
-    // While we want to clean up the table to keep it up from occupying too
-    // much storage and memory, we must keep the highest value in the table
-    // because InnoDB  uses an in-memory auto-increment counter as long as the
-    // server runs. When the server is stopped and restarted, InnoDB
-    // reinitializes the counter for each table for the first INSERT to the
-    // table based solely on values from the table so deleting all values would
-    // be a problem in this case. Also, TRUNCATE resets the auto increment
-    // counter.
-    try {
-      $max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField();
-      // We know we are using MySQL here, no need for the slower db_delete().
-      $this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id));
-    }
-    // During testing, this function is called from shutdown with the
-    // simpletest prefix stored in $this->connection, and those tables are gone
-    // by the time shutdown is called so we need to ignore the database
-    // errors. There is no problem with completely ignoring errors here: if
-    // these queries fail, the sequence will work just fine, just use a bit
-    // more database storage and memory.
-    catch (DatabaseException $e) {
-    }
-  }
-
-  /**
-   * Overridden to work around issues to MySQL not supporting transactional DDL.
-   */
-  protected function popCommittableTransactions() {
-    // Commit all the committable layers.
-    foreach (array_reverse($this->transactionLayers) as $name => $active) {
-      // Stop once we found an active transaction.
-      if ($active) {
-        break;
-      }
-
-      // If there are no more layers left then we should commit.
-      unset($this->transactionLayers[$name]);
-      if (empty($this->transactionLayers)) {
-        if (!$this->connection->commit()) {
-          throw new TransactionCommitFailedException();
-        }
-      }
-      else {
-        // Attempt to release this savepoint in the standard way.
-        try {
-          $this->query('RELEASE SAVEPOINT ' . $name);
-        }
-        catch (DatabaseExceptionWrapper $e) {
-          // However, in MySQL (InnoDB), savepoints are automatically committed
-          // when tables are altered or created (DDL transactions are not
-          // supported). This can cause exceptions due to trying to release
-          // savepoints which no longer exist.
-          //
-          // To avoid exceptions when no actual error has occurred, we silently
-          // succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
-          if ($e->getPrevious()->errorInfo[1] == '1305') {
-            // If one SAVEPOINT was released automatically, then all were.
-            // Therefore, clean the transaction stack.
-            $this->transactionLayers = array();
-            // We also have to explain to PDO that the transaction stack has
-            // been cleaned-up.
-            $this->connection->commit();
-          }
-          else {
-            throw $e;
-          }
-        }
-      }
-    }
-  }
-}
-
-
-/**
- * @} End of "addtogroup database".
- */
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Delete.php b/core/lib/Drupal/Core/Database/Driver/mysql/Delete.php
deleted file mode 100644
index de0db11..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Delete.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Delete
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Query\Delete as QueryDelete;
-
-class Delete extends QueryDelete { }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php b/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
deleted file mode 100644
index c41fdf3..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Insert.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Insert
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Query\Insert as QueryInsert;
-
-class Insert extends QueryInsert {
-
-  public function execute() {
-    if (!$this->preExecute()) {
-      return NULL;
-    }
-
-    // If we're selecting from a SelectQuery, finish building the query and
-    // pass it back, as any remaining options are irrelevant.
-    if (empty($this->fromQuery)) {
-      $max_placeholder = 0;
-      $values = array();
-      foreach ($this->insertValues as $insert_values) {
-        foreach ($insert_values as $value) {
-          $values[':db_insert_placeholder_' . $max_placeholder++] = $value;
-        }
-      }
-    }
-    else {
-      $values = $this->fromQuery->getArguments();
-    }
-
-    $last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
-
-    // Re-initialize the values array so that we can re-use this query.
-    $this->insertValues = array();
-
-    return $last_insert_id;
-  }
-
-  public function __toString() {
-    // Create a sanitized comment string to prepend to the query.
-    $comments = $this->connection->makeComment($this->comments);
-
-    // Default fields are always placed first for consistency.
-    $insert_fields = array_merge($this->defaultFields, $this->insertFields);
-
-    // If we're selecting from a SelectQuery, finish building the query and
-    // pass it back, as any remaining options are irrelevant.
-    if (!empty($this->fromQuery)) {
-      $insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
-      return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
-    }
-
-    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
-
-    $max_placeholder = 0;
-    $values = array();
-    if (count($this->insertValues)) {
-      foreach ($this->insertValues as $insert_values) {
-        $placeholders = array();
-
-        // Default fields aren't really placeholders, but this is the most convenient
-        // way to handle them.
-        $placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
-
-        $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) . ')';
-      }
-    }
-    else {
-      // If there are no values, then this is a default-only query. We still need to handle that.
-      $placeholders = array_fill(0, count($this->defaultFields), 'default');
-      $values[] = '(' . implode(', ', $placeholders) . ')';
-    }
-
-    $query .= implode(', ', $values);
-
-    return $query;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
deleted file mode 100644
index 44de504..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Install/Tasks.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Install\Tasks
- */
-
-namespace Drupal\Core\Database\Driver\mysql\Install;
-
-use Drupal\Core\Database\Install\Tasks as InstallTasks;
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Driver\mysql\Connection;
-use Drupal\Core\Database\DatabaseNotFoundException;
-
-/**
- * Specifies installation tasks for MySQL and equivalent databases.
- */
-class Tasks extends InstallTasks {
-  /**
-   * The PDO driver name for MySQL and equivalent databases.
-   *
-   * @var string
-   */
-  protected $pdoDriver = 'mysql';
-
-  /**
-   * {@inheritdoc}
-   */
-  public function name() {
-    return t('MySQL, MariaDB, Percona Server, or equivalent');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function minimumVersion() {
-    return '5.1.21';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function connect() {
-    try {
-      // This doesn't actually test the connection.
-      db_set_active();
-      // Now actually do a check.
-      Database::getConnection();
-      $this->pass('Drupal can CONNECT to the database ok.');
-    }
-    catch (\Exception $e) {
-      // Attempt to create the database if it is not found.
-      if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
-        // Remove the database string from connection info.
-        $connection_info = Database::getConnectionInfo();
-        $database = $connection_info['default']['database'];
-        unset($connection_info['default']['database']);
-
-        // In order to change the Database::$databaseInfo array, need to remove
-        // the active connection, then re-add it with the new info.
-        Database::removeConnection('default');
-        Database::addConnectionInfo('default', 'default', $connection_info['default']);
-
-        try {
-          // Now, attempt the connection again; if it's successful, attempt to
-          // create the database.
-          Database::getConnection()->createDatabase($database);
-        }
-        catch (DatabaseNotFoundException $e) {
-          // Still no dice; probably a permission issue. Raise the error to the
-          // installer.
-          $this->fail(t('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
-        }
-      }
-      else {
-        // Database connection failed for some other reason than the database
-        // not existing.
-        $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist or does the database user have sufficient privileges to create the database?</li><li>Have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
-        return FALSE;
-      }
-    }
-    return TRUE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormOptions(array $database) {
-    $form = parent::getFormOptions($database);
-    if (empty($form['advanced_options']['port']['#default_value'])) {
-      $form['advanced_options']['port']['#default_value'] = '3306';
-    }
-
-    return $form;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Merge.php b/core/lib/Drupal/Core/Database/Driver/mysql/Merge.php
deleted file mode 100644
index c48b830..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Merge.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Merge
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Query\Merge as QueryMerge;
-
-class Merge extends QueryMerge { }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
deleted file mode 100644
index 10976b2..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
+++ /dev/null
@@ -1,545 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Schema
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Component\Utility\String;
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Database\SchemaObjectExistsException;
-use Drupal\Core\Database\SchemaObjectDoesNotExistException;
-use Drupal\Core\Database\Schema as DatabaseSchema;
-use Drupal\Component\Utility\Unicode;
-
-/**
- * @addtogroup schemaapi
- * @{
- */
-
-class Schema extends DatabaseSchema {
-
-  /**
-   * Maximum length of a table comment in MySQL.
-   */
-  const COMMENT_MAX_TABLE = 60;
-
-  /**
-   * Maximum length of a column comment in MySQL.
-   */
-  const COMMENT_MAX_COLUMN = 255;
-
-  /**
-   * Get information about the table and database name from the prefix.
-   *
-   * @return
-   *   A keyed array with information about the database, table name and prefix.
-   */
-  protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
-    $info = array('prefix' => $this->connection->tablePrefix($table));
-    if ($add_prefix) {
-      $table = $info['prefix'] . $table;
-    }
-    if (($pos = strpos($table, '.')) !== FALSE) {
-      $info['database'] = substr($table, 0, $pos);
-      $info['table'] = substr($table, ++$pos);
-    }
-    else {
-      $db_info = Database::getConnectionInfo();
-      $info['database'] = $db_info[$this->connection->getTarget()]['database'];
-      $info['table'] = $table;
-    }
-    return $info;
-  }
-
-  /**
-   * Build a condition to match a table name against a standard information_schema.
-   *
-   * MySQL uses databases like schemas rather than catalogs so when we build
-   * a condition to query the information_schema.tables, we set the default
-   * database as the schema unless specified otherwise, and exclude table_catalog
-   * from the condition criteria.
-   */
-  protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
-    $table_info = $this->getPrefixInfo($table_name, $add_prefix);
-
-    $condition = new Condition('AND');
-    $condition->condition('table_schema', $table_info['database']);
-    $condition->condition('table_name', $table_info['table'], $operator);
-    return $condition;
-  }
-
-  /**
-   * 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.
-   */
-  protected function createTableSql($name, $table) {
-    $info = $this->connection->getConnectionOptions();
-
-    // Provide defaults if needed.
-    $table += array(
-      'mysql_engine' => 'InnoDB',
-      'mysql_character_set' => 'utf8',
-    );
-
-    $sql = "CREATE TABLE {" . $name . "} (\n";
-
-    // Add the SQL statement for each field.
-    foreach ($table['fields'] as $field_name => $field) {
-      $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
-    }
-
-    // Process keys & indexes.
-    $keys = $this->createKeysSql($table);
-    if (count($keys)) {
-      $sql .= implode(", \n", $keys) . ", \n";
-    }
-
-    // Remove the last comma and space.
-    $sql = substr($sql, 0, -3) . "\n) ";
-
-    $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
-    // By default, MySQL uses the default collation for new tables, which is
-    // 'utf8_general_ci' for utf8. If an alternate collation has been set, it
-    // needs to be explicitly specified.
-    // @see DatabaseConnection_mysql
-    if (!empty($info['collation'])) {
-      $sql .= ' COLLATE ' . $info['collation'];
-    }
-
-    // Add table comment.
-    if (!empty($table['description'])) {
-      $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
-    }
-
-    return array($sql);
-  }
-
-  /**
-   * 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.
-   */
-  protected function createFieldSql($name, $spec) {
-    $sql = "`" . $name . "` " . $spec['mysql_type'];
-
-    if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) {
-      if (isset($spec['length'])) {
-        $sql .= '(' . $spec['length'] . ')';
-      }
-      if (!empty($spec['binary'])) {
-        $sql .= ' BINARY';
-      }
-    }
-    elseif (isset($spec['precision']) && isset($spec['scale'])) {
-      $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
-    }
-
-    if (!empty($spec['unsigned'])) {
-      $sql .= ' unsigned';
-    }
-
-    if (isset($spec['not null'])) {
-      if ($spec['not null']) {
-        $sql .= ' NOT NULL';
-      }
-      else {
-        $sql .= ' NULL';
-      }
-    }
-
-    if (!empty($spec['auto_increment'])) {
-      $sql .= ' auto_increment';
-    }
-
-    // $spec['default'] can be NULL, so we explicitly check for the key here.
-    if (array_key_exists('default', $spec)) {
-      $sql .= ' DEFAULT ' . $this->escapeDefaultValue($spec['default']);
-    }
-
-    if (empty($spec['not null']) && !isset($spec['default'])) {
-      $sql .= ' DEFAULT NULL';
-    }
-
-    // Add column comment.
-    if (!empty($spec['description'])) {
-      $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
-    }
-
-    return $sql;
-  }
-
-  /**
-   * Set database-engine specific properties for a field.
-   *
-   * @param $field
-   *   A field description array, as specified in the schema documentation.
-   */
-  protected function processField($field) {
-
-    if (!isset($field['size'])) {
-      $field['size'] = 'normal';
-    }
-
-    // Set the correct database-engine specific datatype.
-    // In case one is already provided, force it to uppercase.
-    if (isset($field['mysql_type'])) {
-      $field['mysql_type'] = drupal_strtoupper($field['mysql_type']);
-    }
-    else {
-      $map = $this->getFieldTypeMap();
-      $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
-    }
-
-    if (isset($field['type']) && $field['type'] == 'serial') {
-      $field['auto_increment'] = TRUE;
-    }
-
-    return $field;
-  }
-
-  public function getFieldTypeMap() {
-    // 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 does not use drupal_static as its value never changes.
-    static $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',
-    );
-    return $map;
-  }
-
-  protected function createKeysSql($spec) {
-    $keys = array();
-
-    if (!empty($spec['primary key'])) {
-      $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')';
-    }
-    if (!empty($spec['unique keys'])) {
-      foreach ($spec['unique keys'] as $key => $fields) {
-        $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')';
-      }
-    }
-    if (!empty($spec['indexes'])) {
-      foreach ($spec['indexes'] as $index => $fields) {
-        $keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')';
-      }
-    }
-
-    return $keys;
-  }
-
-  protected function createKeySql($fields) {
-    $return = array();
-    foreach ($fields as $field) {
-      if (is_array($field)) {
-        $return[] = '`' . $field[0] . '`(' . $field[1] . ')';
-      }
-      else {
-        $return[] = '`' . $field . '`';
-      }
-    }
-    return implode(', ', $return);
-  }
-
-  protected function createKeysSqlHelper($fields) {
-    $return = array();
-    foreach ($fields as $field) {
-      if (is_array($field)) {
-        $return[] = '`' . $field[0] . '`(' . $field[1] . ')';
-      }
-      else {
-        $return[] = '`' . $field . '`';
-      }
-    }
-    return implode(', ', $return);
-  }
-
-  public function renameTable($table, $new_name) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
-    }
-    if ($this->tableExists($new_name)) {
-      throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
-    }
-
-    $info = $this->getPrefixInfo($new_name);
-    return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
-  }
-
-  public function dropTable($table) {
-    if (!$this->tableExists($table)) {
-      return FALSE;
-    }
-
-    $this->connection->query('DROP TABLE {' . $table . '}');
-    return TRUE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function copyTable($source, $destination) {
-    if (!$this->tableExists($source)) {
-      throw new SchemaObjectDoesNotExistException(String::format("Cannot copy @source to @destination: table @source doesn't exist.", array('@source' => $source, '@destination' => $destination)));
-    }
-    if ($this->tableExists($destination)) {
-      throw new SchemaObjectExistsException(String::format("Cannot copy @source to @destination: table @destination already exists.", array('@source' => $source, '@destination' => $destination)));
-    }
-
-    $info = $this->getPrefixInfo($destination);
-    return $this->connection->query('CREATE TABLE `' . $info['table'] . '` LIKE {' . $source . '}');
-  }
-
-  public function addField($table, $field, $spec, $keys_new = array()) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
-    }
-    if ($this->fieldExists($table, $field)) {
-      throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
-    }
-
-    $fixnull = FALSE;
-    if (!empty($spec['not null']) && !isset($spec['default'])) {
-      $fixnull = TRUE;
-      $spec['not null'] = FALSE;
-    }
-    $query = 'ALTER TABLE {' . $table . '} ADD ';
-    $query .= $this->createFieldSql($field, $this->processField($spec));
-    if ($keys_sql = $this->createKeysSql($keys_new)) {
-      $query .= ', ADD ' . implode(', ADD ', $keys_sql);
-    }
-    $this->connection->query($query);
-    if (isset($spec['initial'])) {
-      $this->connection->update($table)
-        ->fields(array($field => $spec['initial']))
-        ->execute();
-    }
-    if ($fixnull) {
-      $spec['not null'] = TRUE;
-      $this->changeField($table, $field, $field, $spec);
-    }
-  }
-
-  public function dropField($table, $field) {
-    if (!$this->fieldExists($table, $field)) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
-    return TRUE;
-  }
-
-  public function fieldSetDefault($table, $field, $default) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $this->escapeDefaultValue($default));
-  }
-
-  public function fieldSetNoDefault($table, $field) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
-  }
-
-  public function indexExists($table, $name) {
-    // Returns one row for each column in the index. Result is string or FALSE.
-    // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
-    $row = $this->connection->query('SHOW INDEX FROM {' . $table . '} WHERE key_name = ' . $this->connection->quote($name))->fetchAssoc();
-    return isset($row['Key_name']);
-  }
-
-  public function addPrimaryKey($table, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
-    }
-    if ($this->indexExists($table, 'PRIMARY')) {
-      throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
-  }
-
-  public function dropPrimaryKey($table) {
-    if (!$this->indexExists($table, 'PRIMARY')) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
-    return TRUE;
-  }
-
-  public function addUniqueKey($table, $name, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
-    }
-    if ($this->indexExists($table, $name)) {
-      throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
-  }
-
-  public function dropUniqueKey($table, $name) {
-    if (!$this->indexExists($table, $name)) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`');
-    return TRUE;
-  }
-
-  public function addIndex($table, $name, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
-    }
-    if ($this->indexExists($table, $name)) {
-      throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
-  }
-
-  public function dropIndex($table, $name) {
-    if (!$this->indexExists($table, $name)) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
-    return TRUE;
-  }
-
-  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
-    }
-    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
-      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
-    }
-
-    $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
-    if ($keys_sql = $this->createKeysSql($keys_new)) {
-      $sql .= ', ADD ' . implode(', ADD ', $keys_sql);
-    }
-    $this->connection->query($sql);
-  }
-
-  public function prepareComment($comment, $length = NULL) {
-    // Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125
-    $comment = str_replace("'", '’', $comment);
-
-    // Truncate comment to maximum comment length.
-    if (isset($length)) {
-      // Add table prefixes before truncating.
-      $comment = Unicode::truncate($this->connection->prefixTables($comment), $length, TRUE, TRUE);
-    }
-
-    return $this->connection->quote($comment);
-  }
-
-  /**
-   * Retrieve a table or column comment.
-   */
-  public function getComment($table, $column = NULL) {
-    $condition = $this->buildTableNameCondition($table);
-    if (isset($column)) {
-      $condition->condition('column_name', $column);
-      $condition->compile($this->connection, $this);
-      // Don't use {} around information_schema.columns table.
-      return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
-    }
-    $condition->compile($this->connection, $this);
-    // Don't use {} around information_schema.tables table.
-    $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
-    // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
-    return preg_replace('/; InnoDB free:.*$/', '', $comment);
-  }
-
-  public function tableExists($table) {
-    // The information_schema table is very slow to query under MySQL 5.0.
-    // Instead, we try to select from the table in question.  If it fails,
-    // the most likely reason is that it does not exist. That is dramatically
-    // faster than using information_schema.
-    // @link http://bugs.mysql.com/bug.php?id=19588
-    // @todo This override should be removed once we require a version of MySQL
-    //   that has that bug fixed.
-    try {
-      $this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1);
-      return TRUE;
-    }
-    catch (\Exception $e) {
-      return FALSE;
-    }
-  }
-
-  public function fieldExists($table, $column) {
-    // The information_schema table is very slow to query under MySQL 5.0.
-    // Instead, we try to select from the table and field in question. If it
-    // fails, the most likely reason is that it does not exist. That is
-    // dramatically faster than using information_schema.
-    // @link http://bugs.mysql.com/bug.php?id=19588
-    // @todo This override should be removed once we require a version of MySQL
-    //   that has that bug fixed.
-    try {
-      $this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1);
-      return TRUE;
-    }
-    catch (\Exception $e) {
-      return FALSE;
-    }
-  }
-
-}
-
-/**
- * @} End of "addtogroup schemaapi".
- */
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Select.php b/core/lib/Drupal/Core/Database/Driver/mysql/Select.php
deleted file mode 100644
index aecae55..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Select.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Select
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Query\Select as QuerySelect;
-
-class Select extends QuerySelect { }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Transaction.php b/core/lib/Drupal/Core/Database/Driver/mysql/Transaction.php
deleted file mode 100644
index 570a130..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Transaction.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Transaction
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Transaction as DatabaseTransaction;
-
-class Transaction extends DatabaseTransaction { }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Truncate.php b/core/lib/Drupal/Core/Database/Driver/mysql/Truncate.php
deleted file mode 100644
index c7d7a36..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Truncate.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Truncate
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Query\Truncate as QueryTruncate;
-
-class Truncate extends QueryTruncate { }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Update.php b/core/lib/Drupal/Core/Database/Driver/mysql/Update.php
deleted file mode 100644
index 5b3bda6..0000000
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Update.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\mysql\Update
- */
-
-namespace Drupal\Core\Database\Driver\mysql;
-
-use Drupal\Core\Database\Query\Update as QueryUpdate;
-
-class Update extends QueryUpdate { }
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
deleted file mode 100644
index 98b7a6b..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
+++ /dev/null
@@ -1,330 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Connection
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Connection as DatabaseConnection;
-use Drupal\Core\Database\DatabaseNotFoundException;
-use Drupal\Core\Database\StatementInterface;
-use Drupal\Core\Database\IntegrityConstraintViolationException;
-use Drupal\Core\Database\DatabaseExceptionWrapper;
-
-/**
- * @addtogroup database
- * @{
- */
-
-class Connection extends DatabaseConnection {
-
-  /**
-   * The name by which to obtain a lock for retrieve the next insert id.
-   */
-  const POSTGRESQL_NEXTID_LOCK = 1000;
-
-  /**
-   * Error code for "Unknown database" error.
-   */
-  const DATABASE_NOT_FOUND = 7;
-
-  /**
-   * Constructs a connection object.
-   */
-  public function __construct(\PDO $connection, array $connection_options) {
-    parent::__construct($connection, $connection_options);
-
-    // This driver defaults to transaction support, except if explicitly passed FALSE.
-    $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
-
-    // Transactional DDL is always available in PostgreSQL,
-    // but we'll only enable it if standard transactions are.
-    $this->transactionalDDLSupport = $this->transactionSupport;
-
-    $this->connectionOptions = $connection_options;
-
-    // Force PostgreSQL to use the UTF-8 character set by default.
-    $this->connection->exec("SET NAMES 'UTF8'");
-
-    // Execute PostgreSQL init_commands.
-    if (isset($connection_options['init_commands'])) {
-      $this->connection->exec(implode('; ', $connection_options['init_commands']));
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function open(array &$connection_options = array()) {
-    // Default to TCP connection on port 5432.
-    if (empty($connection_options['port'])) {
-      $connection_options['port'] = 5432;
-    }
-
-    // PostgreSQL in trust mode doesn't require a password to be supplied.
-    if (empty($connection_options['password'])) {
-      $connection_options['password'] = NULL;
-    }
-    // If the password contains a backslash it is treated as an escape character
-    // http://bugs.php.net/bug.php?id=53217
-    // so backslashes in the password need to be doubled up.
-    // The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords
-    // will break on this doubling up when the bug is fixed, so check the version
-    //elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') {
-    else {
-      $connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']);
-    }
-
-    $connection_options['database'] = (!empty($connection_options['database']) ? $connection_options['database'] : 'template1');
-    $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
-
-    // Allow PDO options to be overridden.
-    $connection_options += array(
-      'pdo' => array(),
-    );
-    $connection_options['pdo'] += array(
-      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
-      // Prepared statements are most effective for performance when queries
-      // are recycled (used several times). However, if they are not re-used,
-      // prepared statements become inefficient. Since most of Drupal's
-      // prepared queries are not re-used, it should be faster to emulate
-      // the preparation than to actually ready statements for re-use. If in
-      // doubt, reset to FALSE and measure performance.
-      \PDO::ATTR_EMULATE_PREPARES => TRUE,
-      // Convert numeric values to strings when fetching.
-      \PDO::ATTR_STRINGIFY_FETCHES => TRUE,
-    );
-    $pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
-
-    return $pdo;
-  }
-
-  public function query($query, array $args = array(), $options = array()) {
-
-    $options += $this->defaultOptions();
-
-    // The PDO PostgreSQL driver has a bug which
-    // doesn't type cast booleans correctly when
-    // parameters are bound using associative
-    // arrays.
-    // See http://bugs.php.net/bug.php?id=48383
-    foreach ($args as &$value) {
-      if (is_bool($value)) {
-        $value = (int) $value;
-      }
-    }
-
-    try {
-      if ($query instanceof StatementInterface) {
-        $stmt = $query;
-        $stmt->execute(NULL, $options);
-      }
-      else {
-        $this->expandArguments($query, $args);
-        $stmt = $this->prepareQuery($query);
-        $stmt->execute($args, $options);
-      }
-
-      switch ($options['return']) {
-        case Database::RETURN_STATEMENT:
-          return $stmt;
-        case Database::RETURN_AFFECTED:
-          $stmt->allowRowCount = TRUE;
-          return $stmt->rowCount();
-        case Database::RETURN_INSERT_ID:
-          return $this->connection->lastInsertId($options['sequence_name']);
-        case Database::RETURN_NULL:
-          return;
-        default:
-          throw new \PDOException('Invalid return directive: ' . $options['return']);
-      }
-    }
-    catch (\PDOException $e) {
-      if ($options['throw_exception']) {
-        // Match all SQLSTATE 23xxx errors.
-        if (substr($e->getCode(), -6, -3) == '23') {
-          $e = new IntegrityConstraintViolationException($e->getMessage(), $e->getCode(), $e);
-        }
-        else {
-          $e = new DatabaseExceptionWrapper($e->getMessage(), 0, $e);
-        }
-        // Add additional debug information.
-        if ($query instanceof StatementInterface) {
-          $e->query_string = $stmt->getQueryString();
-        }
-        else {
-          $e->query_string = $query;
-        }
-        $e->args = $args;
-        throw $e;
-      }
-      return NULL;
-    }
-  }
-
-  public function prepareQuery($query) {
-    // mapConditionOperator converts LIKE operations to ILIKE for consistency
-    // with MySQL. However, Postgres does not support ILIKE on bytea (blobs)
-    // fields.
-    // To make the ILIKE operator work, we type-cast bytea fields into text.
-    // @todo This workaround only affects bytea fields, but the involved field
-    //   types involved in the query are unknown, so there is no way to
-    //   conditionally execute this for affected queries only.
-    return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query));
-  }
-
-  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
-    return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options);
-  }
-
-  public function queryTemporary($query, array $args = array(), array $options = array()) {
-    $tablename = $this->generateTemporaryTableName();
-    $this->query('CREATE TEMPORARY TABLE {' . $tablename . '} AS ' . $query, $args, $options);
-    return $tablename;
-  }
-
-  public function driver() {
-    return 'pgsql';
-  }
-
-  public function databaseType() {
-    return 'pgsql';
-  }
-
-  /**
-   * Overrides \Drupal\Core\Database\Connection::createDatabase().
-   *
-   * @param string $database
-   *   The name of the database to create.
-   *
-   * @throws \Drupal\Core\Database\DatabaseNotFoundException
-   */
-  public function createDatabase($database) {
-    // Escape the database name.
-    $database = Database::getConnection()->escapeDatabase($database);
-
-    // If the PECL intl extension is installed, use it to determine the proper
-    // locale.  Otherwise, fall back to en_US.
-    if (class_exists('Locale')) {
-      $locale = \Locale::getDefault();
-    }
-    else {
-      $locale = 'en_US';
-    }
-
-    try {
-      // Create the database and set it as active.
-      $this->connection->exec("CREATE DATABASE $database WITH TEMPLATE template0 ENCODING='utf8' LC_CTYPE='$locale.utf8' LC_COLLATE='$locale.utf8'");
-    }
-    catch (\Exception $e) {
-      throw new DatabaseNotFoundException($e->getMessage());
-    }
-  }
-
-  public function mapConditionOperator($operator) {
-    static $specials = array(
-      // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
-      // statements, we need to use ILIKE instead.
-      'LIKE' => array('operator' => 'ILIKE'),
-      'NOT LIKE' => array('operator' => 'NOT ILIKE'),
-      'REGEXP' => array('operator' => '~*'),
-    );
-    return isset($specials[$operator]) ? $specials[$operator] : NULL;
-  }
-
-  /**
-   * Retrieve a the next id in a sequence.
-   *
-   * PostgreSQL has built in sequences. We'll use these instead of inserting
-   * and updating a sequences table.
-   */
-  public function nextId($existing = 0) {
-
-    // Retrieve the name of the sequence. This information cannot be cached
-    // because the prefix may change, for example, like it does in simpletests.
-    $sequence_name = $this->makeSequenceName('sequences', 'value');
-
-    // When PostgreSQL gets a value too small then it will lock the table,
-    // retry the INSERT and if it's still too small then alter the sequence.
-    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
-    if ($id > $existing) {
-      return $id;
-    }
-
-    // PostgreSQL advisory locks are simply locks to be used by an
-    // application such as Drupal. This will prevent other Drupal processes
-    // from altering the sequence while we are.
-    $this->query("SELECT pg_advisory_lock(" . self::POSTGRESQL_NEXTID_LOCK . ")");
-
-    // While waiting to obtain the lock, the sequence may have been altered
-    // so lets try again to obtain an adequate value.
-    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
-    if ($id > $existing) {
-      $this->query("SELECT pg_advisory_unlock(" . self::POSTGRESQL_NEXTID_LOCK . ")");
-      return $id;
-    }
-
-    // Reset the sequence to a higher value than the existing id.
-    $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1));
-
-    // Retrieve the next id. We know this will be as high as we want it.
-    $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
-
-    $this->query("SELECT pg_advisory_unlock(" . self::POSTGRESQL_NEXTID_LOCK . ")");
-
-    return $id;
-  }
-
-  /**
-   * Add a new savepoint with an unique name.
-   *
-   * The main use for this method is to mimic InnoDB functionality, which
-   * provides an inherent savepoint before any query in a transaction.
-   *
-   * @param $savepoint_name
-   *   A string representing the savepoint name. By default,
-   *   "mimic_implicit_commit" is used.
-   *
-   * @see Drupal\Core\Database\Connection::pushTransaction().
-   */
-  public function addSavepoint($savepoint_name = 'mimic_implicit_commit') {
-    if ($this->inTransaction()) {
-      $this->pushTransaction($savepoint_name);
-    }
-  }
-
-  /**
-   * Release a savepoint by name.
-   *
-   * @param $savepoint_name
-   *   A string representing the savepoint name. By default,
-   *   "mimic_implicit_commit" is used.
-   *
-   * @see Drupal\Core\Database\Connection::popTransaction().
-   */
-  public function releaseSavepoint($savepoint_name = 'mimic_implicit_commit') {
-    if (isset($this->transactionLayers[$savepoint_name])) {
-      $this->popTransaction($savepoint_name);
-    }
-  }
-
-  /**
-   * Rollback a savepoint by name if it exists.
-   *
-   * @param $savepoint_name
-   *   A string representing the savepoint name. By default,
-   *   "mimic_implicit_commit" is used.
-   */
-  public function rollbackSavepoint($savepoint_name = 'mimic_implicit_commit') {
-    if (isset($this->transactionLayers[$savepoint_name])) {
-      $this->rollback($savepoint_name);
-    }
-  }
-}
-
-/**
- * @} End of "addtogroup database".
- */
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php
deleted file mode 100644
index 85c5003..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Delete.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Delete
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Query\Delete as QueryDelete;
-
-class Delete extends QueryDelete {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    $this->connection->addSavepoint();
-    try {
-      $result = parent::execute();
-    }
-    catch (\Exception $e) {
-      $this->connection->rollbackSavepoint();
-      throw $e;
-    }
-    $this->connection->releaseSavepoint();
-
-    return $result;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php
deleted file mode 100644
index fba74da..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Insert.php
+++ /dev/null
@@ -1,157 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Insert
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Query\Insert as QueryInsert;
-
-/**
- * @ingroup database
- * @{
- */
-
-class Insert extends QueryInsert {
-
-  public function execute() {
-    if (!$this->preExecute()) {
-      return NULL;
-    }
-
-    $stmt = $this->connection->prepareQuery((string) $this);
-
-    // Fetch the list of blobs and sequences used on that table.
-    $table_information = $this->connection->schema()->queryTableInformation($this->table);
-
-    $max_placeholder = 0;
-    $blobs = array();
-    $blob_count = 0;
-    foreach ($this->insertValues as $insert_values) {
-      foreach ($this->insertFields as $idx => $field) {
-        if (isset($table_information->blob_fields[$field])) {
-          $blobs[$blob_count] = fopen('php://memory', 'a');
-          fwrite($blobs[$blob_count], $insert_values[$idx]);
-          rewind($blobs[$blob_count]);
-
-          $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], \PDO::PARAM_LOB);
-
-          // Pre-increment is faster in PHP than increment.
-          ++$blob_count;
-        }
-        else {
-          $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
-        }
-      }
-      // Check if values for a serial field has been passed.
-      if (!empty($table_information->serial_fields)) {
-        foreach ($table_information->serial_fields as $index => $serial_field) {
-          $serial_key = array_search($serial_field, $this->insertFields);
-          if ($serial_key !== FALSE) {
-            $serial_value = $insert_values[$serial_key];
-
-            // Force $last_insert_id to the specified value. This is only done
-            // if $index is 0.
-            if ($index == 0) {
-              $last_insert_id = $serial_value;
-            }
-            // Sequences must be greater than or equal to 1.
-            if ($serial_value === NULL || !$serial_value) {
-              $serial_value = 1;
-            }
-            // Set the sequence to the bigger value of either the passed
-            // value or the max value of the column. It can happen that another
-            // thread calls nextval() which could lead to a serial number being
-            // used twice. However, trying to insert a value into a serial
-            // column should only be done in very rare cases and is not thread
-            // safe by definition.
-            $this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value));
-          }
-        }
-      }
-    }
-    if (!empty($this->fromQuery)) {
-      // bindParam stores only a reference to the variable that is followed when
-      // the statement is executed. We pass $arguments[$key] instead of $value
-      // because the second argument to bindParam is passed by reference and
-      // the foreach statement assigns the element to the existing reference.
-      $arguments = $this->fromQuery->getArguments();
-      foreach ($arguments as $key => $value) {
-        $stmt->bindParam($key, $arguments[$key]);
-      }
-    }
-
-    // PostgreSQL requires the table name to be specified explicitly
-    // when requesting the last insert ID, so we pass that in via
-    // the options array.
-    $options = $this->queryOptions;
-
-    if (!empty($table_information->sequences)) {
-      $options['sequence_name'] = $table_information->sequences[0];
-    }
-    // If there are no sequences then we can't get a last insert id.
-    elseif ($options['return'] == Database::RETURN_INSERT_ID) {
-      $options['return'] = Database::RETURN_NULL;
-    }
-    // Only use the returned last_insert_id if it is not already set.
-    if (!empty($last_insert_id)) {
-      $this->connection->query($stmt, array(), $options);
-    }
-    else {
-      $last_insert_id = $this->connection->query($stmt, array(), $options);
-    }
-
-    // Re-initialize the values array so that we can re-use this query.
-    $this->insertValues = array();
-
-    return $last_insert_id;
-  }
-
-  public function __toString() {
-    // Create a sanitized comment string to prepend to the query.
-    $comments = $this->connection->makeComment($this->comments);
-
-    // Default fields are always placed first for consistency.
-    $insert_fields = array_merge($this->defaultFields, $this->insertFields);
-
-    // If we're selecting from a SelectQuery, finish building the query and
-    // pass it back, as any remaining options are irrelevant.
-    if (!empty($this->fromQuery)) {
-      $insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
-      return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
-    }
-
-    $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
-
-    $max_placeholder = 0;
-    $values = array();
-      if (count($this->insertValues)) {
-      foreach ($this->insertValues as $insert_values) {
-        $placeholders = array();
-
-        // Default fields aren't really placeholders, but this is the most convenient
-        // way to handle them.
-        $placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
-
-        $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) . ')';
-      }
-    }
-    else {
-      // If there are no values, then this is a default-only query. We still need to handle that.
-      $placeholders = array_fill(0, count($this->defaultFields), 'default');
-      $values[] = '(' . implode(', ', $placeholders) . ')';
-    }
-
-    $query .= implode(', ', $values);
-
-    return $query;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
deleted file mode 100644
index e4e8d7e..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Install/Tasks.php
+++ /dev/null
@@ -1,258 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Install\Tasks
- */
-
-namespace Drupal\Core\Database\Driver\pgsql\Install;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Install\Tasks as InstallTasks;
-use Drupal\Core\Database\Driver\pgsql\Connection;
-use Drupal\Core\Database\DatabaseNotFoundException;
-
-/**
- * Specifies installation tasks for PostgreSQL databases.
- */
-class Tasks extends InstallTasks {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $pdoDriver = 'pgsql';
-
-  /**
-   * Constructs a \Drupal\Core\Database\Driver\pgsql\Install\Tasks object.
-   */
-  public function __construct() {
-    $this->tasks[] = array(
-      'function' => 'checkEncoding',
-      'arguments' => array(),
-    );
-    $this->tasks[] = array(
-      'function' => 'checkBinaryOutput',
-      'arguments' => array(),
-    );
-    $this->tasks[] = array(
-      'function' => 'initializeDatabase',
-      'arguments' => array(),
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function name() {
-    return t('PostgreSQL');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function minimumVersion() {
-    return '8.3';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function connect() {
-    try {
-      // This doesn't actually test the connection.
-      db_set_active();
-      // Now actually do a check.
-      Database::getConnection();
-      $this->pass('Drupal can CONNECT to the database ok.');
-    }
-    catch (\Exception $e) {
-    // Attempt to create the database if it is not found.
-      if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
-        // Remove the database string from connection info.
-        $connection_info = Database::getConnectionInfo();
-        $database = $connection_info['default']['database'];
-        unset($connection_info['default']['database']);
-
-        // In order to change the Database::$databaseInfo array, need to remove
-        // the active connection, then re-add it with the new info.
-        Database::removeConnection('default');
-        Database::addConnectionInfo('default', 'default', $connection_info['default']);
-
-        try {
-          // Now, attempt the connection again; if it's successful, attempt to
-          // create the database.
-          Database::getConnection()->createDatabase($database);
-          Database::closeConnection();
-
-          // Now, restore the database config.
-          Database::removeConnection('default');
-          $connection_info['default']['database'] = $database;
-          Database::addConnectionInfo('default', 'default', $connection_info['default']);
-
-          // Check the database connection.
-          Database::getConnection();
-          $this->pass('Drupal can CONNECT to the database ok.');
-        }
-        catch (DatabaseNotFoundException $e) {
-          // Still no dice; probably a permission issue. Raise the error to the
-          // installer.
-          $this->fail(t('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
-        }
-      }
-      else {
-        // Database connection failed for some other reason than the database
-        // not existing.
-        $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
-        return FALSE;
-      }
-    }
-    return TRUE;
-  }
-
-  /**
-   * Check encoding is UTF8.
-   */
-  protected function checkEncoding() {
-    try {
-      if (db_query('SHOW server_encoding')->fetchField() == 'UTF8') {
-        $this->pass(t('Database is encoded in UTF-8'));
-      }
-      else {
-        $this->fail(t('The %driver database must use %encoding encoding to work with Drupal. Recreate the database with %encoding encoding. See !link for more details.', array(
-          '%encoding' => 'UTF8',
-          '%driver' => $this->name(),
-          '!link' => '<a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a>'
-        )));
-      }
-    }
-    catch (\Exception $e) {
-      $this->fail(t('Drupal could not determine the encoding of the database was set to UTF-8'));
-    }
-  }
-
-  /**
-   * Check Binary Output.
-   *
-   * Unserializing does not work on Postgresql 9 when bytea_output is 'hex'.
-   */
-  function checkBinaryOutput() {
-    // PostgreSQL < 9 doesn't support bytea_output, so verify we are running
-    // at least PostgreSQL 9.
-    $database_connection = Database::getConnection();
-    if (version_compare($database_connection->version(), '9') >= 0) {
-      if (!$this->checkBinaryOutputSuccess()) {
-        // First try to alter the database. If it fails, raise an error telling
-        // the user to do it themselves.
-        $connection_options = $database_connection->getConnectionOptions();
-        // It is safe to include the database name directly here, because this
-        // code is only called when a connection to the database is already
-        // established, thus the database name is guaranteed to be a correct
-        // value.
-        $query = "ALTER DATABASE \"" . $connection_options['database'] . "\" SET bytea_output = 'escape';";
-        try {
-          db_query($query);
-        }
-        catch (\Exception $e) {
-          // Ignore possible errors when the user doesn't have the necessary
-          // privileges to ALTER the database.
-        }
-
-        // Close the database connection so that the configuration parameter
-        // is applied to the current connection.
-        db_close();
-
-        // Recheck, if it fails, finally just rely on the end user to do the
-        // right thing.
-        if (!$this->checkBinaryOutputSuccess()) {
-          $replacements = array(
-            '%setting' => 'bytea_output',
-            '%current_value' => 'hex',
-            '%needed_value' => 'escape',
-            '!query' => "<code>" . $query . "</code>",
-          );
-          $this->fail(t("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: !query", $replacements));
-        }
-      }
-    }
-  }
-
-  /**
-   * Verify that a binary data roundtrip returns the original string.
-   */
-  protected function checkBinaryOutputSuccess() {
-    $bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
-    return ($bytea_output == 'encoding');
-  }
-
-  /**
-   * Make PostgreSQL Drupal friendly.
-   */
-  function initializeDatabase() {
-    // We create some functions using global names instead of prefixing them
-    // like we do with table names. This is so that we don't double up if more
-    // than one instance of Drupal is running on a single database. We therefore
-    // avoid trying to create them again in that case.
-
-    try {
-      // Create functions.
-      db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
-        \'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
-        LANGUAGE \'sql\''
-      );
-      db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
-        \'SELECT greatest($1, greatest($2, $3));\'
-        LANGUAGE \'sql\''
-      );
-      // Don't use {} around pg_proc table.
-      if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
-        db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
-          \'SELECT random();\'
-          LANGUAGE \'sql\''
-        );
-      }
-
-      db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
-        \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
-        LANGUAGE \'sql\''
-      );
-
-      // Using || to concatenate in Drupal is not recommended because there are
-      // database drivers for Drupal that do not support the syntax, however
-      // they do support CONCAT(item1, item2) which we can replicate in
-      // PostgreSQL. PostgreSQL requires the function to be defined for each
-      // different argument variation the function can handle.
-      db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS
-        \'SELECT CAST($1 AS text) || CAST($2 AS text);\'
-        LANGUAGE \'sql\'
-      ');
-      db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS
-        \'SELECT $1 || CAST($2 AS text);\'
-        LANGUAGE \'sql\'
-      ');
-      db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS
-        \'SELECT CAST($1 AS text) || $2;\'
-        LANGUAGE \'sql\'
-      ');
-      db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
-        \'SELECT $1 || $2;\'
-        LANGUAGE \'sql\'
-      ');
-
-      $this->pass(t('PostgreSQL has initialized itself.'));
-    }
-    catch (\Exception $e) {
-      $this->fail(t('Drupal could not be correctly setup with the existing database. Revise any errors.'));
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormOptions(array $database) {
-    $form = parent::getFormOptions($database);
-    if (empty($form['advanced_options']['port']['#default_value'])) {
-      $form['advanced_options']['port']['#default_value'] = '5432';
-    }
-    return $form;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Merge.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Merge.php
deleted file mode 100644
index c3a2ae0..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Merge.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Merge
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Query\Merge as QueryMerge;
-
-class Merge extends QueryMerge { }
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
deleted file mode 100644
index 4938601..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
+++ /dev/null
@@ -1,750 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Schema
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Component\Utility\String;
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Query\Condition;
-use Drupal\Core\Database\SchemaObjectExistsException;
-use Drupal\Core\Database\SchemaObjectDoesNotExistException;
-use Drupal\Core\Database\Schema as DatabaseSchema;
-
-/**
- * @addtogroup schemaapi
- * @{
- */
-
-class Schema extends DatabaseSchema {
-
-  /**
-   * A cache of information about blob columns and sequences of tables.
-   *
-   * This is collected by DatabaseConnection_pgsql->queryTableInformation(),
-   * by introspecting the database.
-   *
-   * @see DatabaseConnection_pgsql->queryTableInformation()
-   * @var array
-   */
-  protected $tableInformation = array();
-
-  /**
-   * The maximum allowed length for index, primary key and constraint names.
-   *
-   * Value will usually be set to a 63 chars limit but PostgreSQL allows
-   * to higher this value before compiling, so we need to check for that.
-   *
-   * @var integer
-   */
-  protected $maxIdentifierLength;
-
-  /**
-   * Make sure to limit identifiers according to PostgreSQL compiled in length.
-   *
-   * PostgreSQL allows in standard configuration no longer identifiers than 63
-   * chars for table/relation names, indexes, primary keys, and constraints. So
-   * we map all identifiers that are too long to drupal_base64hash_tag, where
-   * tag is one of:
-   *   - idx for indexes
-   *   - key for constraints
-   *   - pkey for primary keys
-   *
-   * @param $identifiers
-   *   The arguments to build the identifier string
-   * @return
-   *   The index/constraint/pkey identifier
-   */
-  protected function ensureIdentifiersLength($identifier) {
-    $args = func_get_args();
-    $info = $this->getPrefixInfo($identifier);
-    $args[0] = $info['table'];
-    $identifierName = implode('__', $args);
-
-    // Retrieve the max identifier length which is usually 63 characters
-    // but can be altered before PostgreSQL is compiled so we need to check.
-    $this->maxIdentifierLength = $this->connection->query("SHOW max_identifier_length")->fetchField();
-
-    if (strlen($identifierName) > $this->maxIdentifierLength) {
-      $saveIdentifier = 'drupal_' . $this->hashBase64($identifierName) . '_' . $args[2];
-    }
-    else {
-      $saveIdentifier = $identifierName;
-    }
-    return $saveIdentifier;
-  }
-
-  /**
-   * Fetch the list of blobs and sequences used on a table.
-   *
-   * We introspect the database to collect the information required by insert
-   * and update queries.
-   *
-   * @param $table_name
-   *   The non-prefixed name of the table.
-   * @return
-   *   An object with two member variables:
-   *     - 'blob_fields' that lists all the blob fields in the table.
-   *     - 'sequences' that lists the sequences used in that table.
-   */
-  public function queryTableInformation($table) {
-    // Generate a key to reference this table's information on.
-    $key = $this->connection->prefixTables('{' . $table . '}');
-    if (!strpos($key, '.')) {
-      $key = 'public.' . $key;
-    }
-
-    if (!isset($this->tableInformation[$key])) {
-      // Split the key into schema and table for querying.
-      list($schema, $table_name) = explode('.', $key);
-      $table_information = (object) array(
-        'blob_fields' => array(),
-        'sequences' => array(),
-      );
-      // Don't use {} around information_schema.columns table.
-      $this->connection->addSavepoint();
-
-      try {
-        $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
-          ':schema' => $schema,
-          ':table' => $table_name,
-          ':default' => '%nextval%',
-        ));
-      }
-      catch (\Exception $e) {
-        $this->connection->rollbackSavepoint();
-        throw $e;
-      }
-      $this->connection->releaseSavepoint();
-
-      foreach ($result as $column) {
-        if ($column->data_type == 'bytea') {
-          $table_information->blob_fields[$column->column_name] = TRUE;
-        }
-        elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
-          // We must know of any sequences in the table structure to help us
-          // return the last insert id. If there is more than 1 sequences the
-          // first one (index 0 of the sequences array) will be used.
-          $table_information->sequences[] = $matches[1];
-          $table_information->serial_fields[] = $column->column_name;
-        }
-      }
-      $this->tableInformation[$key] = $table_information;
-    }
-    return $this->tableInformation[$key];
-  }
-
-  /**
-   * Fetch the list of CHECK constraints used on a field.
-   *
-   * We introspect the database to collect the information required by field
-   * alteration.
-   *
-   * @param $table
-   *   The non-prefixed name of the table.
-   * @param $field
-   *   The name of the field.
-   * @return
-   *   An array of all the checks for the field.
-   */
-  public function queryFieldInformation($table, $field) {
-    $prefixInfo = $this->getPrefixInfo($table, TRUE);
-
-    // Split the key into schema and table for querying.
-    $schema = $prefixInfo['schema'];
-    $table_name = $prefixInfo['table'];
-
-    $this->connection->addSavepoint();
-
-    try {
-      $checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array(
-        ':schema' => $schema,
-        ':table' => $table_name,
-        ':column' => $field,
-      ));
-    }
-    catch (\Exception $e) {
-      $this->connection->rollbackSavepoint();
-      throw $e;
-    }
-
-    $this->connection->releaseSavepoint();
-
-    $field_information = $checks->fetchCol();
-
-    return $field_information;
-  }
-
-  /**
-   * 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.
-   */
-  protected function createTableSql($name, $table) {
-    $sql_fields = array();
-    foreach ($table['fields'] as $field_name => $field) {
-      $sql_fields[] = $this->createFieldSql($field_name, $this->processField($field));
-    }
-
-    $sql_keys = array();
-    if (isset($table['primary key']) && is_array($table['primary key'])) {
-      $sql_keys[] = 'PRIMARY KEY (' . $this->createPrimaryKeySql($table['primary key']) . ')';
-    }
-    if (isset($table['unique keys']) && is_array($table['unique keys'])) {
-      foreach ($table['unique keys'] as $key_name => $key) {
-        $sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($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[] = $this->_createIndexSql($name, $key_name, $key);
-      }
-    }
-
-    // Add table comment.
-    if (!empty($table['description'])) {
-      $statements[] = 'COMMENT ON TABLE {' . $name . '} IS ' . $this->prepareComment($table['description']);
-    }
-
-    // Add column comments.
-    foreach ($table['fields'] as $field_name => $field) {
-      if (!empty($field['description'])) {
-        $statements[] = 'COMMENT ON COLUMN {' . $name . '}.' . $field_name . ' IS ' . $this->prepareComment($field['description']);
-      }
-    }
-
-    return $statements;
-  }
-
-  /**
-   * 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.
-   */
-  protected function createFieldSql($name, $spec) {
-    $sql = $name . ' ' . $spec['pgsql_type'];
-
-    if (isset($spec['type']) && $spec['type'] == 'serial') {
-      unset($spec['not null']);
-    }
-
-    if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
-      $sql .= '(' . $spec['length'] . ')';
-    }
-    elseif (isset($spec['precision']) && isset($spec['scale'])) {
-      $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
-    }
-
-    if (!empty($spec['unsigned'])) {
-      $sql .= " CHECK ($name >= 0)";
-    }
-
-    if (isset($spec['not null'])) {
-      if ($spec['not null']) {
-        $sql .= ' NOT NULL';
-      }
-      else {
-        $sql .= ' NULL';
-      }
-    }
-    if (array_key_exists('default', $spec)) {
-      $default = $this->escapeDefaultValue($spec['default']);
-      $sql .= " default $default";
-    }
-
-    return $sql;
-  }
-
-  /**
-   * Set database-engine specific properties for a field.
-   *
-   * @param $field
-   *   A field description array, as specified in the schema documentation.
-   */
-  protected function processField($field) {
-    if (!isset($field['size'])) {
-      $field['size'] = 'normal';
-    }
-
-    // Set the correct database-engine specific datatype.
-    // In case one is already provided, force it to lowercase.
-    if (isset($field['pgsql_type'])) {
-      $field['pgsql_type'] = drupal_strtolower($field['pgsql_type']);
-    }
-    else {
-      $map = $this->getFieldTypeMap();
-      $field['pgsql_type'] = $map[$field['type'] . ':' . $field['size']];
-    }
-
-    if (!empty($field['unsigned'])) {
-      // Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL,
-      // they are used to ensure a positive number is inserted and it also
-      // doubles the maximum integer size that can be stored in a field.
-      // The PostgreSQL schema in Drupal creates a check constraint
-      // to ensure that a value inserted is >= 0. To provide the extra
-      // integer capacity, here, we bump up the column field size.
-      if (!isset($map)) {
-        $map = $this->getFieldTypeMap();
-      }
-      switch ($field['pgsql_type']) {
-        case 'smallint':
-          $field['pgsql_type'] = $map['int:medium'];
-          break;
-        case 'int' :
-          $field['pgsql_type'] = $map['int:big'];
-          break;
-      }
-    }
-    if (isset($field['type']) && $field['type'] == 'serial') {
-      unset($field['not null']);
-    }
-    return $field;
-  }
-
-  /**
-   * This maps a generic data type in combination with its data size
-   * to the engine-specific data type.
-   */
-  function getFieldTypeMap() {
-    // 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 does not use drupal_static as its value never changes.
-    static $map = array(
-      'varchar:normal' => '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',
-
-      'serial:tiny' => 'serial',
-      'serial:small' => 'serial',
-      'serial:medium' => 'serial',
-      'serial:big' => 'bigserial',
-      'serial:normal' => 'serial',
-      );
-    return $map;
-  }
-
-  protected function _createKeySql($fields) {
-    $return = array();
-    foreach ($fields as $field) {
-      if (is_array($field)) {
-        $return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')';
-      }
-      else {
-        $return[] = '"' . $field . '"';
-      }
-    }
-    return implode(', ', $return);
-  }
-
-  /**
-   * Create the SQL expression for primary keys.
-   *
-   * Postgresql does not support key length. It does support fillfactor, but
-   * that requires a separate database lookup for each column in the key. The
-   * key length defined in the schema is ignored.
-   */
-  protected function createPrimaryKeySql($fields) {
-    $return = array();
-    foreach ($fields as $field) {
-      if (is_array($field)) {
-        $return[] = '"' . $field[0] . '"';
-      }
-      else {
-        $return[] = '"' . $field . '"';
-      }
-    }
-    return implode(', ', $return);
-  }
-
-  function renameTable($table, $new_name) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
-    }
-    if ($this->tableExists($new_name)) {
-      throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
-    }
-
-    // Get the schema and tablename for the old table.
-    $old_full_name = $this->connection->prefixTables('{' . $table . '}');
-    list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : array('public', $old_full_name);
-
-    // Index names and constraint names are global in PostgreSQL, so we need to
-    // rename them when renaming the table.
-    $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name));
-    foreach ($indexes as $index) {
-      if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) {
-        $index_name = $matches[1];
-        $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO ' . $this->ensureIdentifiersLength($new_name, $index_name, 'idx'));
-      }
-    }
-
-    // Now rename the table.
-    // Ensure the new table name does not include schema syntax.
-    $prefixInfo = $this->getPrefixInfo($new_name);
-    $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function copyTable($source, $destination) {
-    // @TODO The server is likely going to rename indexes and constraints
-    //   during the copy process, and it will not match our
-    //   table_name + constraint name convention anymore.
-    throw new \Exception('Not implemented, see https://drupal.org/node/2061879');
-  }
-
-  public function dropTable($table) {
-    if (!$this->tableExists($table)) {
-      return FALSE;
-    }
-
-    $this->connection->query('DROP TABLE {' . $table . '}');
-    return TRUE;
-  }
-
-  public function addField($table, $field, $spec, $new_keys = array()) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
-    }
-    if ($this->fieldExists($table, $field)) {
-      throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
-    }
-
-    $fixnull = FALSE;
-    if (!empty($spec['not null']) && !isset($spec['default'])) {
-      $fixnull = TRUE;
-      $spec['not null'] = FALSE;
-    }
-    $query = 'ALTER TABLE {' . $table . '} ADD COLUMN ';
-    $query .= $this->createFieldSql($field, $this->processField($spec));
-    $this->connection->query($query);
-    if (isset($spec['initial'])) {
-      $this->connection->update($table)
-        ->fields(array($field => $spec['initial']))
-        ->execute();
-    }
-    if ($fixnull) {
-      $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL");
-    }
-    if (isset($new_keys)) {
-      $this->_createKeys($table, $new_keys);
-    }
-    // Add column comment.
-    if (!empty($spec['description'])) {
-      $this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description']));
-    }
-  }
-
-  public function dropField($table, $field) {
-    if (!$this->fieldExists($table, $field)) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"');
-    return TRUE;
-  }
-
-  public function fieldSetDefault($table, $field, $default) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
-    }
-
-    $default = $this->escapeDefaultValue($default);
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default);
-  }
-
-  public function fieldSetNoDefault($table, $field) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
-  }
-
-  public function indexExists($table, $name) {
-    // Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
-    $index_name = $this->ensureIdentifiersLength($table, $name, 'idx');
-    return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField();
-  }
-
-  /**
-   * Helper function: check if a constraint (PK, FK, UK) exists.
-   *
-   * @param $table
-   *   The name of the table.
-   * @param $name
-   *   The name of the constraint (typically 'pkey' or '[constraint]_key').
-   */
-  protected function constraintExists($table, $name) {
-    $constraint_name = $this->ensureIdentifiersLength($table, $name);
-    return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField();
-  }
-
-  public function addPrimaryKey($table, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
-    }
-    if ($this->constraintExists($table, 'pkey')) {
-      throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createPrimaryKeySql($fields) . ')');
-  }
-
-  public function dropPrimaryKey($table) {
-    if (!$this->constraintExists($table, 'pkey')) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->ensureIdentifiersLength($table, 'pkey'));
-    return TRUE;
-  }
-
-  function addUniqueKey($table, $name, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
-    }
-    if ($this->constraintExists($table, $name . '_key')) {
-      throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
-  }
-
-  public function dropUniqueKey($table, $name) {
-    if (!$this->constraintExists($table, $name . '_key')) {
-      return FALSE;
-    }
-
-    $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'key') . '"');
-    return TRUE;
-  }
-
-  public function addIndex($table, $name, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
-    }
-    if ($this->indexExists($table, $name)) {
-      throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
-    }
-
-    $this->connection->query($this->_createIndexSql($table, $name, $fields));
-  }
-
-  public function dropIndex($table, $name) {
-    if (!$this->indexExists($table, $name)) {
-      return FALSE;
-    }
-
-    $this->connection->query('DROP INDEX ' . $this->ensureIdentifiersLength($table, $name, 'idx'));
-    return TRUE;
-  }
-
-  public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
-    }
-    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
-      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
-    }
-
-    $spec = $this->processField($spec);
-
-    // We need to typecast the new column to best be able to transfer the data
-    // Schema_pgsql::getFieldTypeMap() will return possibilities that are not
-    // 'cast-able' such as 'serial' - so they need to be casted int instead.
-    if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
-      $typecast = 'int';
-    }
-    else {
-      $typecast = $spec['pgsql_type'];
-    }
-
-    if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
-      $typecast .= '(' . $spec['length'] . ')';
-    }
-    elseif (isset($spec['precision']) && isset($spec['scale'])) {
-      $typecast .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
-    }
-
-    // Remove old check constraints.
-    $field_info = $this->queryFieldInformation($table, $field);
-
-    foreach ($field_info as $check) {
-      $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"');
-    }
-
-    // Remove old default.
-    $this->fieldSetNoDefault($table, $field);
-
-    // Convert field type.
-    // Usually, we do this via a simple typecast 'USING fieldname::type'. But
-    // the typecast does not work for conversions to bytea.
-    // @see http://www.postgresql.org/docs/current/static/datatype-binary.html
-    if ($spec['pgsql_type'] != 'bytea') {
-      $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast);
-    }
-    else {
-      // Do not attempt to convert a field that is bytea already.
-      $table_information = $this->queryTableInformation($table);
-      if (!in_array($field, $table_information->blob_fields)) {
-        // Convert to a bytea type by using the SQL replace() function to
-        // convert any single backslashes in the field content to double
-        // backslashes ('\' to '\\').
-        $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING decode(replace("' . $field . '"' . ", '\\', '\\\\'), 'escape');");
-      }
-    }
-
-    if (isset($spec['not null'])) {
-      if ($spec['not null']) {
-        $nullaction = 'SET NOT NULL';
-      }
-      else {
-        $nullaction = 'DROP NOT NULL';
-      }
-      $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" ' . $nullaction);
-    }
-
-    if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
-      // Type "serial" is known to PostgreSQL, but *only* during table creation,
-      // not when altering. Because of that, the sequence needs to be created
-      // and initialized by hand.
-      $seq = "{" . $table . "}_" . $field_new . "_seq";
-      $this->connection->query("CREATE SEQUENCE " . $seq);
-      // Set sequence to maximal field value to not conflict with existing
-      // entries.
-      $this->connection->query("SELECT setval('" . $seq . "', MAX(\"" . $field . '")) FROM {' . $table . "}");
-      $this->connection->query('ALTER TABLE {' . $table . '} ALTER ' . $field . ' SET DEFAULT nextval(' . $this->connection->quote($seq) . ')');
-    }
-
-    // Rename the column if necessary.
-    if ($field != $field_new) {
-      $this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"');
-    }
-
-    // Add unsigned check if necessary.
-    if (!empty($spec['unsigned'])) {
-      $this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)');
-    }
-
-    // Add default if necessary.
-    if (isset($spec['default'])) {
-      $this->fieldSetDefault($table, $field_new, $spec['default']);
-    }
-
-    // Change description if necessary.
-    if (!empty($spec['description'])) {
-      $this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description']));
-    }
-
-    if (isset($new_keys)) {
-      $this->_createKeys($table, $new_keys);
-    }
-  }
-
-  protected function _createIndexSql($table, $name, $fields) {
-    $query = 'CREATE INDEX "' . $this->ensureIdentifiersLength($table, $name, 'idx') . '" ON {' . $table . '} (';
-    $query .= $this->_createKeySql($fields) . ')';
-    return $query;
-  }
-
-  protected function _createKeys($table, $new_keys) {
-    if (isset($new_keys['primary key'])) {
-      $this->addPrimaryKey($table, $new_keys['primary key']);
-    }
-    if (isset($new_keys['unique keys'])) {
-      foreach ($new_keys['unique keys'] as $name => $fields) {
-        $this->addUniqueKey($table, $name, $fields);
-      }
-    }
-    if (isset($new_keys['indexes'])) {
-      foreach ($new_keys['indexes'] as $name => $fields) {
-        $this->addIndex($table, $name, $fields);
-      }
-    }
-  }
-
-  /**
-   * Retrieve a table or column comment.
-   */
-  public function getComment($table, $column = NULL) {
-    $info = $this->getPrefixInfo($table);
-    // Don't use {} around pg_class, pg_attribute tables.
-    if (isset($column)) {
-      return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
-    }
-    else {
-      return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
-    }
-  }
-
-  /**
-   * Calculates a base-64 encoded, PostgreSQL-safe sha-256 hash per PostgreSQL
-   * documentation: 4.1. Lexical Structure.
-   *
-   * @param $data
-   *   String to be hashed.
-   * @return string
-   *   A base-64 encoded sha-256 hash, with + and / replaced with _ and any =
-   *   padding characters removed.
-   */
-  protected function hashBase64($data) {
-    $hash = base64_encode(hash('sha256', $data, TRUE));
-    // Modify the hash so it's safe to use in PostgreSQL identifiers.
-    return strtr($hash, array('+' => '_', '/' => '_', '=' => ''));
-  }
-}
-
-/**
- * @} End of "addtogroup schemaapi".
- */
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php
deleted file mode 100644
index de9409f..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Select
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Query\Select as QuerySelect;
-
-/**
- * @addtogroup database
- * @{
- */
-
-class Select extends QuerySelect {
-
-  public function orderRandom() {
-    $alias = $this->addExpression('RANDOM()', 'random_field');
-    $this->orderBy($alias);
-    return $this;
-  }
-
-  /**
-   * Overrides SelectQuery::orderBy().
-   *
-   * PostgreSQL adheres strictly to the SQL-92 standard and requires that when
-   * using DISTINCT or GROUP BY conditions, fields and expressions that are
-   * ordered on also need to be selected. This is a best effort implementation
-   * to handle the cases that can be automated by adding the field if it is not
-   * yet selected.
-   *
-   * @code
-   *   $query = db_select('example', 'e');
-   *   $query->join('example_revision', 'er', 'e.vid = er.vid');
-   *   $query
-   *     ->distinct()
-   *     ->fields('e')
-   *     ->orderBy('timestamp');
-   * @endcode
-   *
-   * In this query, it is not possible (without relying on the schema) to know
-   * whether timestamp belongs to example_revision and needs to be added or
-   * belongs to node and is already selected. Queries like this will need to be
-   * corrected in the original query by adding an explicit call to
-   * SelectQuery::addField() or SelectQuery::fields().
-   *
-   * Since this has a small performance impact, both by the additional
-   * processing in this function and in the database that needs to return the
-   * additional fields, this is done as an override instead of implementing it
-   * directly in SelectQuery::orderBy().
-   */
-  public function orderBy($field, $direction = 'ASC') {
-    // Call parent function to order on this.
-    $return = parent::orderBy($field, $direction);
-
-    if ($this->hasTag('entity_query')) {
-      return $return;
-    }
-
-    // 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 ambiguous 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 handled 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;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    $this->connection->addSavepoint();
-    try {
-      $result = parent::execute();
-    }
-    catch (\Exception $e) {
-      $this->connection->rollbackSavepoint();
-      throw $e;
-    }
-    $this->connection->releaseSavepoint();
-
-    return $result;
-  }
-}
-
-/**
- * @} End of "addtogroup database".
- */
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Transaction.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Transaction.php
deleted file mode 100644
index 8c3f701..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Transaction.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Transaction
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Transaction as DatabaseTransaction;
-
-class Transaction extends DatabaseTransaction { }
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php
deleted file mode 100644
index 5357e6b..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Truncate.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Truncate
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Query\Truncate as QueryTruncate;
-
-class Truncate extends QueryTruncate {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute() {
-    $this->connection->addSavepoint();
-    try {
-      $result = parent::execute();
-    }
-    catch (\Exception $e) {
-      $this->connection->rollbackSavepoint();
-      throw $e;
-    }
-    $this->connection->releaseSavepoint();
-
-    return $result;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php
deleted file mode 100644
index 1bee32d..0000000
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Update.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\pgsql\Update
- */
-
-namespace Drupal\Core\Database\Driver\pgsql;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Query\Update as QueryUpdate;
-use Drupal\Core\Database\Query\SelectInterface;
-
-class Update extends QueryUpdate {
-
-  public function execute() {
-    $max_placeholder = 0;
-    $blobs = array();
-    $blob_count = 0;
-
-    // Because we filter $fields the same way here and in __toString(), the
-    // placeholders will all match up properly.
-    $stmt = $this->connection->prepareQuery((string) $this);
-
-    // Fetch the list of blobs and sequences used on that table.
-    $table_information = $this->connection->schema()->queryTableInformation($this->table);
-
-    // Expressions take priority over literal fields, so we process those first
-    // and remove any literal fields that conflict.
-    $fields = $this->fields;
-    foreach ($this->expressionFields as $field => $data) {
-      if (!empty($data['arguments'])) {
-        foreach ($data['arguments'] as $placeholder => $argument) {
-          // We assume that an expression will never happen on a BLOB field,
-          // which is a fairly safe assumption to make since in most cases
-          // it would be an invalid query anyway.
-          $stmt->bindParam($placeholder, $data['arguments'][$placeholder]);
-        }
-      }
-      if ($data['expression'] instanceof SelectInterface) {
-        $data['expression']->compile($this->connection, $this);
-        $select_query_arguments = $data['expression']->arguments();
-        foreach ($select_query_arguments as $placeholder => $argument) {
-          $stmt->bindParam($placeholder, $select_query_arguments[$placeholder]);
-        }
-      }
-      unset($fields[$field]);
-    }
-
-    foreach ($fields as $field => $value) {
-      $placeholder = ':db_update_placeholder_' . ($max_placeholder++);
-
-      if (isset($table_information->blob_fields[$field])) {
-        $blobs[$blob_count] = fopen('php://memory', 'a');
-        fwrite($blobs[$blob_count], $value);
-        rewind($blobs[$blob_count]);
-        $stmt->bindParam($placeholder, $blobs[$blob_count], \PDO::PARAM_LOB);
-        ++$blob_count;
-      }
-      else {
-        $stmt->bindParam($placeholder, $fields[$field]);
-      }
-    }
-
-    if (count($this->condition)) {
-      $this->condition->compile($this->connection, $this);
-
-      $arguments = $this->condition->arguments();
-      foreach ($arguments as $placeholder => $value) {
-        $stmt->bindParam($placeholder, $arguments[$placeholder]);
-      }
-    }
-
-    $options = $this->queryOptions;
-    $options['already_prepared'] = TRUE;
-    $options['return'] = Database::RETURN_AFFECTED;
-
-    $this->connection->addSavepoint();
-    try {
-      $result = $this->connection->query($stmt, array(), $options);
-      $this->connection->releaseSavepoint();
-      return $result;
-    }
-    catch (\Exception $e) {
-      $this->connection->rollbackSavepoint();
-      throw $e;
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
deleted file mode 100644
index bca72c4..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Connection.php
+++ /dev/null
@@ -1,411 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Connection
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\DatabaseNotFoundException;
-use Drupal\Core\Database\TransactionNoActiveException;
-use Drupal\Core\Database\TransactionNameNonUniqueException;
-use Drupal\Core\Database\TransactionCommitFailedException;
-use Drupal\Core\Database\Driver\sqlite\Statement;
-use Drupal\Core\Database\Connection as DatabaseConnection;
-
-/**
- * Specific SQLite implementation of DatabaseConnection.
- */
-class Connection extends DatabaseConnection {
-
-  /**
-   * Whether this database connection supports savepoints.
-   *
-   * Version of sqlite lower then 3.6.8 can't use savepoints.
-   * See http://www.sqlite.org/releaselog/3_6_8.html
-   *
-   * @var boolean
-   */
-  protected $savepointSupport = FALSE;
-
-  /**
-   * Error code for "Unable to open database file" error.
-   */
-  const DATABASE_NOT_FOUND = 14;
-
-  /**
-   * Whether or not the active transaction (if any) will be rolled back.
-   *
-   * @var boolean
-   */
-  protected $willRollback;
-
-  /**
-   * All databases attached to the current database. This is used to allow
-   * prefixes to be safely handled without locking the table
-   *
-   * @var array
-   */
-  protected $attachedDatabases = array();
-
-  /**
-   * Whether or not a table has been dropped this request: the destructor will
-   * only try to get rid of unnecessary databases if there is potential of them
-   * being empty.
-   *
-   * This variable is set to public because Schema needs to
-   * access it. However, it should not be manually set.
-   *
-   * @var boolean
-   */
-  var $tableDropped = FALSE;
-
-  /**
-   * Constructs a \Drupal\Core\Database\Driver\sqlite\Connection object.
-   */
-  public function __construct(\PDO $connection, array $connection_options) {
-    parent::__construct($connection, $connection_options);
-
-    // We don't need a specific PDOStatement class here, we simulate it below.
-    $this->statementClass = NULL;
-
-    // This driver defaults to transaction support, except if explicitly passed FALSE.
-    $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
-
-    $this->connectionOptions = $connection_options;
-
-    // Attach one database for each registered prefix.
-    $prefixes = $this->prefixes;
-    foreach ($prefixes as &$prefix) {
-      // Empty prefix means query the main database -- no need to attach anything.
-      if (!empty($prefix)) {
-        // Only attach the database once.
-        if (!isset($this->attachedDatabases[$prefix])) {
-          $this->attachedDatabases[$prefix] = $prefix;
-          $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix));
-        }
-
-        // Add a ., so queries become prefix.table, which is proper syntax for
-        // querying an attached database.
-        $prefix .= '.';
-      }
-    }
-    // Regenerate the prefixes replacement table.
-    $this->setPrefix($prefixes);
-
-    // Detect support for SAVEPOINT.
-    $version = $this->query('SELECT sqlite_version()')->fetchField();
-    $this->savepointSupport = (version_compare($version, '3.6.8') >= 0);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function open(array &$connection_options = array()) {
-    // Allow PDO options to be overridden.
-    $connection_options += array(
-      'pdo' => array(),
-    );
-    $connection_options['pdo'] += array(
-      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
-      // Convert numeric values to strings when fetching.
-      \PDO::ATTR_STRINGIFY_FETCHES => TRUE,
-    );
-    $pdo = new \PDO('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
-
-    // Create functions needed by SQLite.
-    $pdo->sqliteCreateFunction('if', array(__CLASS__, 'sqlFunctionIf'));
-    $pdo->sqliteCreateFunction('greatest', array(__CLASS__, 'sqlFunctionGreatest'));
-    $pdo->sqliteCreateFunction('pow', 'pow', 2);
-    $pdo->sqliteCreateFunction('length', 'strlen', 1);
-    $pdo->sqliteCreateFunction('md5', 'md5', 1);
-    $pdo->sqliteCreateFunction('concat', array(__CLASS__, 'sqlFunctionConcat'));
-    $pdo->sqliteCreateFunction('substring', array(__CLASS__, 'sqlFunctionSubstring'), 3);
-    $pdo->sqliteCreateFunction('substring_index', array(__CLASS__, 'sqlFunctionSubstringIndex'), 3);
-    $pdo->sqliteCreateFunction('rand', array(__CLASS__, 'sqlFunctionRand'));
-    $pdo->sqliteCreateFunction('regexp', array(__CLASS__, 'sqlFunctionRegexp'));
-
-    // Execute sqlite init_commands.
-    if (isset($connection_options['init_commands'])) {
-      $pdo->exec(implode('; ', $connection_options['init_commands']));
-    }
-
-    return $pdo;
-  }
-
-
-  /**
-   * Destructor for the SQLite connection.
-   *
-   * We prune empty databases on destruct, but only if tables have been
-   * dropped. This is especially needed when running the test suite, which
-   * creates and destroy databases several times in a row.
-   */
-  public function __destruct() {
-    if ($this->tableDropped && !empty($this->attachedDatabases)) {
-      foreach ($this->attachedDatabases as $prefix) {
-        // Check if the database is now empty, ignore the internal SQLite tables.
-        try {
-          $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
-
-          // We can prune the database file if it doesn't have any tables.
-          if ($count == 0) {
-            // Detach the database.
-            $this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
-            // Destroy the database file.
-            unlink($this->connectionOptions['database'] . '-' . $prefix);
-          }
-        }
-        catch (\Exception $e) {
-          // Ignore the exception and continue. There is nothing we can do here
-          // to report the error or fail safe.
-        }
-      }
-    }
-  }
-
-  /**
-   * SQLite compatibility implementation for the IF() SQL function.
-   */
-  public static function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
-    return $condition ? $expr1 : $expr2;
-  }
-
-  /**
-   * SQLite compatibility implementation for the GREATEST() SQL function.
-   */
-  public static function sqlFunctionGreatest() {
-    $args = func_get_args();
-    foreach ($args as $v) {
-      if (!isset($v)) {
-        unset($args);
-      }
-    }
-    if (count($args)) {
-      return max($args);
-    }
-    else {
-      return NULL;
-    }
-  }
-
-  /**
-   * SQLite compatibility implementation for the CONCAT() SQL function.
-   */
-  public static function sqlFunctionConcat() {
-    $args = func_get_args();
-    return implode('', $args);
-  }
-
-  /**
-   * SQLite compatibility implementation for the SUBSTRING() SQL function.
-   */
-  public static function sqlFunctionSubstring($string, $from, $length) {
-    return substr($string, $from - 1, $length);
-  }
-
-  /**
-   * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
-   */
-  public static function sqlFunctionSubstringIndex($string, $delimiter, $count) {
-    // If string is empty, simply return an empty string.
-    if (empty($string)) {
-      return '';
-    }
-    $end = 0;
-    for ($i = 0; $i < $count; $i++) {
-      $end = strpos($string, $delimiter, $end + 1);
-      if ($end === FALSE) {
-        $end = strlen($string);
-      }
-    }
-    return substr($string, 0, $end);
-  }
-
-  /**
-   * SQLite compatibility implementation for the RAND() SQL function.
-   */
-  public static function sqlFunctionRand($seed = NULL) {
-    if (isset($seed)) {
-      mt_srand($seed);
-    }
-    return mt_rand() / mt_getrandmax();
-  }
-
-  /**
-   * SQLite compatibility implementation for the REGEXP SQL operator.
-   *
-   * The REGEXP operator is a special syntax for the regexp() user function.
-   */
-  public static function sqlFunctionRegexp($string, $pattern) {
-    return preg_match('#' . str_replace('#', '\#', $pattern) . '#i', $string);
-  }
-
-  /**
-   * SQLite-specific implementation of DatabaseConnection::prepare().
-   *
-   * We don't use prepared statements at all at this stage. We just create
-   * a Statement object, that will create a PDOStatement
-   * using the semi-private PDOPrepare() method below.
-   */
-  public function prepare($statement, array $driver_options = array()) {
-    return new Statement($this->connection, $this, $statement, $driver_options);
-  }
-
-  public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
-    return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
-  }
-
-  public function queryTemporary($query, array $args = array(), array $options = array()) {
-    // Generate a new temporary table name and protect it from prefixing.
-    // SQLite requires that temporary tables to be non-qualified.
-    $tablename = $this->generateTemporaryTableName();
-    $prefixes = $this->prefixes;
-    $prefixes[$tablename] = '';
-    $this->setPrefix($prefixes);
-
-    $this->query('CREATE TEMPORARY TABLE ' . $tablename . ' AS ' . $query, $args, $options);
-    return $tablename;
-  }
-
-  public function driver() {
-    return 'sqlite';
-  }
-
-  public function databaseType() {
-    return 'sqlite';
-  }
-
-  /**
-   * Overrides \Drupal\Core\Database\Connection::createDatabase().
-   *
-   * @param string $database
-   *   The name of the database to create.
-   *
-   * @throws \Drupal\Core\Database\DatabaseNotFoundException
-   */
-  public function createDatabase($database) {
-    // Verify the database is writable.
-    $db_directory = new \SplFileInfo(dirname($database));
-    if (!$db_directory->isDir() && !drupal_mkdir($db_directory->getPathName(), 0755, TRUE)) {
-      throw new DatabaseNotFoundException('Unable to create database directory ' . $db_directory->getPathName());
-    }
-  }
-
-  public function mapConditionOperator($operator) {
-    // We don't want to override any of the defaults.
-    static $specials = array(
-      'LIKE' => array('postfix' => " ESCAPE '\\'"),
-      'NOT LIKE' => array('postfix' => " ESCAPE '\\'"),
-    );
-    return isset($specials[$operator]) ? $specials[$operator] : NULL;
-  }
-
-  public function nextId($existing_id = 0) {
-    $this->startTransaction();
-    // We can safely use literal queries here instead of the slower query
-    // builder because if a given database breaks here then it can simply
-    // override nextId. However, this is unlikely as we deal with short strings
-    // and integers and no known databases require special handling for those
-    // simple cases. If another transaction wants to write the same row, it will
-    // wait until this transaction commits. Also, the return value needs to be
-    // set to RETURN_AFFECTED as if it were a real update() query otherwise it
-    // is not possible to get the row count properly.
-    $affected = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array(
-      ':existing_id' => $existing_id,
-    ), array('return' => Database::RETURN_AFFECTED));
-    if (!$affected) {
-      $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array(
-        ':existing_id' => $existing_id,
-      ));
-    }
-    // The transaction gets committed when the transaction object gets destroyed
-    // because it gets out of scope.
-    return $this->query('SELECT value FROM {sequences}')->fetchField();
-  }
-
-  public function rollback($savepoint_name = 'drupal_transaction') {
-    if ($this->savepointSupport) {
-      return parent::rollBack($savepoint_name);
-    }
-
-    if (!$this->inTransaction()) {
-      throw new TransactionNoActiveException();
-    }
-    // A previous rollback to an earlier savepoint may mean that the savepoint
-    // in question has already been rolled back.
-    if (!isset($this->transactionLayers[$savepoint_name])) {
-      return;
-    }
-
-    // We need to find the point we're rolling back to, all other savepoints
-    // before are no longer needed.
-    while ($savepoint = array_pop($this->transactionLayers)) {
-      if ($savepoint == $savepoint_name) {
-        // Mark whole stack of transactions as needed roll back.
-        $this->willRollback = TRUE;
-        // If it is the last the transaction in the stack, then it is not a
-        // savepoint, it is the transaction itself so we will need to roll back
-        // the transaction rather than a savepoint.
-        if (empty($this->transactionLayers)) {
-          break;
-        }
-        return;
-      }
-    }
-    if ($this->supportsTransactions()) {
-      $this->connection->rollBack();
-    }
-  }
-
-  public function pushTransaction($name) {
-    if ($this->savepointSupport) {
-      return parent::pushTransaction($name);
-    }
-    if (!$this->supportsTransactions()) {
-      return;
-    }
-    if (isset($this->transactionLayers[$name])) {
-      throw new TransactionNameNonUniqueException($name . " is already in use.");
-    }
-    if (!$this->inTransaction()) {
-      $this->connection->beginTransaction();
-    }
-    $this->transactionLayers[$name] = $name;
-  }
-
-  public function popTransaction($name) {
-    if ($this->savepointSupport) {
-      return parent::popTransaction($name);
-    }
-    if (!$this->supportsTransactions()) {
-      return;
-    }
-    if (!$this->inTransaction()) {
-      throw new TransactionNoActiveException();
-    }
-
-    // Commit everything since SAVEPOINT $name.
-    while($savepoint = array_pop($this->transactionLayers)) {
-      if ($savepoint != $name) continue;
-
-      // If there are no more layers left then we should commit or rollback.
-      if (empty($this->transactionLayers)) {
-        // If there was any rollback() we should roll back whole transaction.
-        if ($this->willRollback) {
-          $this->willRollback = FALSE;
-          $this->connection->rollBack();
-        }
-        elseif (!$this->connection->commit()) {
-          throw new TransactionCommitFailedException();
-        }
-      }
-      else {
-        break;
-      }
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Delete.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Delete.php
deleted file mode 100644
index c0056cb..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Delete.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Delete
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Query\Delete as QueryDelete;
-
-/**
- * SQLite specific implementation of DeleteQuery.
- *
- * When the WHERE is omitted from a DELETE statement and the table being deleted
- * has no triggers, SQLite uses an optimization to erase the entire table content
- * without having to visit each row of the table individually.
- *
- * Prior to SQLite 3.6.5, SQLite does not return the actual number of rows deleted
- * by that optimized "truncate" optimization.
- */
-class Delete extends QueryDelete {
-  public function execute() {
-    if (!count($this->condition)) {
-      $total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField();
-      parent::execute();
-      return $total_rows;
-    }
-    else {
-      return parent::execute();
-    }
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php
deleted file mode 100644
index 3a0bcaa..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Insert.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Insert
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Query\Insert as QueryInsert;
-
-/**
- * SQLite specific implementation of InsertQuery.
- *
- * We ignore all the default fields and use the clever SQLite syntax:
- *   INSERT INTO table DEFAULT VALUES
- * for degenerated "default only" queries.
- */
-class Insert extends QueryInsert {
-
-  public function execute() {
-    if (!$this->preExecute()) {
-      return NULL;
-    }
-    if (count($this->insertFields)) {
-      return parent::execute();
-    }
-    else {
-      return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', array(), $this->queryOptions);
-    }
-  }
-
-  public function __toString() {
-    // Create a sanitized comment string to prepend to the query.
-    $comments = $this->connection->makeComment($this->comments);
-
-    // Produce as many generic placeholders as necessary.
-    $placeholders = array_fill(0, count($this->insertFields), '?');
-
-    // If we're selecting from a SelectQuery, finish building the query and
-    // pass it back, as any remaining options are irrelevant.
-    if (!empty($this->fromQuery)) {
-      $insert_fields_string = $this->insertFields ? ' (' . implode(', ', $this->insertFields) . ') ' : ' ';
-      return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
-    }
-
-    return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')';
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
deleted file mode 100644
index 97eab54..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Install/Tasks.php
+++ /dev/null
@@ -1,112 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Install\Tasks
- */
-
-namespace Drupal\Core\Database\Driver\sqlite\Install;
-
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\Driver\sqlite\Connection;
-use Drupal\Core\Database\DatabaseNotFoundException;
-use Drupal\Core\Database\Install\Tasks as InstallTasks;
-
-/**
- * Specifies installation tasks for SQLite databases.
- */
-class Tasks extends InstallTasks {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected $pdoDriver = 'sqlite';
-
-  /**
-   * {@inheritdoc}
-   */
-  public function name() {
-    return t('SQLite');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function minimumVersion() {
-    // @todo Consider upping to 3.6.8 in Drupal 8 to get SAVEPOINT support.
-    return '3.3.7';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormOptions(array $database) {
-    $form = parent::getFormOptions($database);
-
-    // Remove the options that only apply to client/server style databases.
-    unset($form['username'], $form['password'], $form['advanced_options']['host'], $form['advanced_options']['port']);
-
-    // Make the text more accurate for SQLite.
-    $form['database']['#title'] = t('Database file');
-    $form['database']['#description'] = t('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
-    $default_database = conf_path(FALSE) . '/files/.ht.sqlite';
-    $form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database'];
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function connect() {
-    try {
-      // This doesn't actually test the connection.
-      db_set_active();
-      // Now actually do a check.
-      Database::getConnection();
-      $this->pass('Drupal can CONNECT to the database ok.');
-    }
-    catch (\Exception $e) {
-      // Attempt to create the database if it is not found.
-      if ($e->getCode() == Connection::DATABASE_NOT_FOUND) {
-        // Remove the database string from connection info.
-        $connection_info = Database::getConnectionInfo();
-        $database = $connection_info['default']['database'];
-
-        // We cannot use file_directory_temp() here because we haven't yet
-        // successfully connected to the database.
-        $connection_info['default']['database'] = drupal_tempnam(sys_get_temp_dir(), 'sqlite');
-
-        // In order to change the Database::$databaseInfo array, need to remove
-        // the active connection, then re-add it with the new info.
-        Database::removeConnection('default');
-        Database::addConnectionInfo('default', 'default', $connection_info['default']);
-
-        try {
-          Database::getConnection()->createDatabase($database);
-          Database::closeConnection();
-
-          // Now, restore the database config.
-          Database::removeConnection('default');
-          $connection_info['default']['database'] = $database;
-          Database::addConnectionInfo('default', 'default', $connection_info['default']);
-
-          // Check the database connection.
-          Database::getConnection();
-          $this->pass('Drupal can CONNECT to the database ok.');
-        }
-        catch (DatabaseNotFoundException $e) {
-          // Still no dice; probably a permission issue. Raise the error to the
-          // installer.
-          $this->fail(t('Database %database not found. The server reports the following message when attempting to create the database: %error.', array('%database' => $database, '%error' => $e->getMessage())));
-        }
-      }
-      else {
-        // Database connection failed for some other reason than the database
-        // not existing.
-        $this->fail(t('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
-        return FALSE;
-      }
-    }
-    return TRUE;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Merge.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Merge.php
deleted file mode 100644
index 3bbc3de..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Merge.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Merge
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Query\Merge as QueryMerge;
-
-class Merge extends QueryMerge { }
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
deleted file mode 100644
index 669a722..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Schema.php
+++ /dev/null
@@ -1,703 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Schema
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Component\Utility\String;
-use Drupal\Core\Database\SchemaObjectExistsException;
-use Drupal\Core\Database\SchemaObjectDoesNotExistException;
-use Drupal\Core\Database\Schema as DatabaseSchema;
-
-/**
- * @ingroup schemaapi
- * @{
- */
-
-class Schema extends DatabaseSchema {
-
-  /**
-   * Override DatabaseSchema::$defaultSchema
-   */
-  protected $defaultSchema = 'main';
-
-  public function tableExists($table) {
-    $info = $this->getPrefixInfo($table);
-
-    // Don't use {} around sqlite_master table.
-    return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', array(':type' => 'table', ':name' => $info['table']))->fetchField();
-  }
-
-  public function fieldExists($table, $column) {
-    $schema = $this->introspectSchema($table);
-    return !empty($schema['fields'][$column]);
-  }
-
-  /**
-   * 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.
-   */
-  public function createTableSql($name, $table) {
-    $sql = array();
-    $sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumnsSql($name, $table) . "\n);\n";
-    return array_merge($sql, $this->createIndexSql($name, $table));
-  }
-
-  /**
-   * Build the SQL expression for indexes.
-   */
-  protected function createIndexSql($tablename, $schema) {
-    $sql = array();
-    $info = $this->getPrefixInfo($tablename);
-    if (!empty($schema['unique keys'])) {
-      foreach ($schema['unique keys'] as $key => $fields) {
-        $sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
-      }
-    }
-    if (!empty($schema['indexes'])) {
-      foreach ($schema['indexes'] as $key => $fields) {
-        $sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
-      }
-    }
-    return $sql;
-  }
-
-  /**
-   * Build the SQL expression for creating columns.
-   */
-  protected function createColumnsSql($tablename, $schema) {
-    $sql_array = array();
-
-    // Add the SQL statement for each field.
-    foreach ($schema['fields'] as $name => $field) {
-      if (isset($field['type']) && $field['type'] == 'serial') {
-        if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) {
-          unset($schema['primary key'][$key]);
-        }
-      }
-      $sql_array[] = $this->createFieldSql($name, $this->processField($field));
-    }
-
-    // Process keys.
-    if (!empty($schema['primary key'])) {
-      $sql_array[] = " PRIMARY KEY (" . $this->createKeySql($schema['primary key']) . ")";
-    }
-
-    return implode(", \n", $sql_array);
-  }
-
-  /**
-   * Build the SQL expression for keys.
-   */
-  protected function createKeySql($fields) {
-    $return = array();
-    foreach ($fields as $field) {
-      if (is_array($field)) {
-        $return[] = $field[0];
-      }
-      else {
-        $return[] = $field;
-      }
-    }
-    return implode(', ', $return);
-  }
-
-  /**
-   * Set database-engine specific properties for a field.
-   *
-   * @param $field
-   *   A field description array, as specified in the schema documentation.
-   */
-  protected function processField($field) {
-    if (!isset($field['size'])) {
-      $field['size'] = 'normal';
-    }
-
-    // Set the correct database-engine specific datatype.
-    // In case one is already provided, force it to uppercase.
-    if (isset($field['sqlite_type'])) {
-      $field['sqlite_type'] = drupal_strtoupper($field['sqlite_type']);
-    }
-    else {
-      $map = $this->getFieldTypeMap();
-      $field['sqlite_type'] = $map[$field['type'] . ':' . $field['size']];
-    }
-
-    if (isset($field['type']) && $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_processField().
-   *
-   * @param $name
-   *    Name of the field.
-   * @param $spec
-   *    The field specification, as per the schema data structure format.
-   */
-  protected function createFieldSql($name, $spec) {
-    if (!empty($spec['auto_increment'])) {
-      $sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT";
-      if (!empty($spec['unsigned'])) {
-        $sql .= ' CHECK (' . $name . '>= 0)';
-      }
-    }
-    else {
-      $sql = $name . ' ' . $spec['sqlite_type'];
-
-      if (in_array($spec['sqlite_type'], array('VARCHAR', 'TEXT')) && isset($spec['length'])) {
-        $sql .= '(' . $spec['length'] . ')';
-      }
-
-      if (isset($spec['not null'])) {
-        if ($spec['not null']) {
-          $sql .= ' NOT NULL';
-        }
-        else {
-          $sql .= ' NULL';
-        }
-      }
-
-      if (!empty($spec['unsigned'])) {
-        $sql .= ' CHECK (' . $name . '>= 0)';
-      }
-
-      if (isset($spec['default'])) {
-        if (is_string($spec['default'])) {
-          $spec['default'] = $this->connection->quote($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.
-   */
-  public function getFieldTypeMap() {
-    // 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 does not use drupal_static as its value never changes.
-    static $map = array(
-      'varchar:normal'  => 'VARCHAR',
-      'char:normal'     => 'CHAR',
-
-      'text:tiny'       => 'TEXT',
-      'text:small'      => 'TEXT',
-      'text:medium'     => 'TEXT',
-      'text:big'        => 'TEXT',
-      'text:normal'     => 'TEXT',
-
-      'serial:tiny'     => 'INTEGER',
-      'serial:small'    => 'INTEGER',
-      'serial:medium'   => 'INTEGER',
-      'serial:big'      => 'INTEGER',
-      'serial:normal'   => 'INTEGER',
-
-      'int:tiny'        => 'INTEGER',
-      'int:small'       => 'INTEGER',
-      'int:medium'      => 'INTEGER',
-      'int:big'         => 'INTEGER',
-      'int:normal'      => 'INTEGER',
-
-      'float:tiny'      => 'FLOAT',
-      'float:small'     => 'FLOAT',
-      'float:medium'    => 'FLOAT',
-      'float:big'       => 'FLOAT',
-      'float:normal'    => 'FLOAT',
-
-      'numeric:normal'  => 'NUMERIC',
-
-      'blob:big'        => 'BLOB',
-      'blob:normal'     => 'BLOB',
-    );
-    return $map;
-  }
-
-  public function renameTable($table, $new_name) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", array('@table' => $table, '@table_new' => $new_name)));
-    }
-    if ($this->tableExists($new_name)) {
-      throw new SchemaObjectExistsException(t("Cannot rename @table to @table_new: table @table_new already exists.", array('@table' => $table, '@table_new' => $new_name)));
-    }
-
-    $schema = $this->introspectSchema($table);
-
-    // SQLite doesn't allow you to rename tables outside of the current
-    // database. So the syntax '...RENAME TO database.table' would fail.
-    // So we must determine the full table name here rather than surrounding
-    // the table with curly braces in case the db_prefix contains a reference
-    // to a database outside of our existing database.
-    $info = $this->getPrefixInfo($new_name);
-    $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']);
-
-    // Drop the indexes, there is no RENAME INDEX command in SQLite.
-    if (!empty($schema['unique keys'])) {
-      foreach ($schema['unique keys'] as $key => $fields) {
-        $this->dropIndex($table, $key);
-      }
-    }
-    if (!empty($schema['indexes'])) {
-      foreach ($schema['indexes'] as $index => $fields) {
-        $this->dropIndex($table, $index);
-      }
-    }
-
-    // Recreate the indexes.
-    $statements = $this->createIndexSql($new_name, $schema);
-    foreach ($statements as $statement) {
-      $this->connection->query($statement);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function copyTable($source, $destination) {
-    if (!$this->tableExists($source)) {
-      throw new SchemaObjectDoesNotExistException(String::format("Cannot copy @source to @destination: table @source doesn't exist.", array('@source' => $source, '@destination' => $destination)));
-    }
-    if ($this->tableExists($destination)) {
-      throw new SchemaObjectExistsException(String::format("Cannot copy @source to @destination: table @destination already exists.", array('@source' => $source, '@destination' => $destination)));
-    }
-
-    $this->createTable($destination, $this->introspectSchema($source));
-  }
-
-  public function dropTable($table) {
-    if (!$this->tableExists($table)) {
-      return FALSE;
-    }
-    $this->connection->tableDropped = TRUE;
-    $this->connection->query('DROP TABLE {' . $table . '}');
-    return TRUE;
-  }
-
-  public function addField($table, $field, $specification, $keys_new = array()) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add field @table.@field: table doesn't exist.", array('@field' => $field, '@table' => $table)));
-    }
-    if ($this->fieldExists($table, $field)) {
-      throw new SchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
-    }
-
-    // SQLite doesn't have a full-featured ALTER TABLE statement. It only
-    // supports adding new fields to a table, in some simple cases. In most
-    // cases, we have to create a new table and copy the data over.
-    if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) {
-      // When we don't have to create new keys and we are not creating a
-      // NOT NULL column without a default value, we can use the quicker version.
-      $query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification));
-      $this->connection->query($query);
-
-      // Apply the initial value if set.
-      if (isset($specification['initial'])) {
-        $this->connection->update($table)
-          ->fields(array($field => $specification['initial']))
-          ->execute();
-      }
-    }
-    else {
-      // We cannot add the field directly. Use the slower table alteration
-      // method, starting from the old schema.
-      $old_schema = $this->introspectSchema($table);
-      $new_schema = $old_schema;
-
-      // Add the new field.
-      $new_schema['fields'][$field] = $specification;
-
-      // Build the mapping between the old fields and the new fields.
-      $mapping = array();
-      if (isset($specification['initial'])) {
-        // If we have a initial value, copy it over.
-        $mapping[$field] = array(
-          'expression' => ':newfieldinitial',
-          'arguments' => array(':newfieldinitial' => $specification['initial']),
-        );
-      }
-      else {
-        // Else use the default of the field.
-        $mapping[$field] = NULL;
-      }
-
-      // Add the new indexes.
-      $new_schema += $keys_new;
-
-      $this->alterTable($table, $old_schema, $new_schema, $mapping);
-    }
-  }
-
-  /**
-   * Create a table with a new schema containing the old content.
-   *
-   * As SQLite does not support ALTER TABLE (with a few exceptions) it is
-   * necessary to create a new table and copy over the old content.
-   *
-   * @param $table
-   *   Name of the table to be altered.
-   * @param $old_schema
-   *   The old schema array for the table.
-   * @param $new_schema
-   *   The new schema array for the table.
-   * @param $mapping
-   *   An optional mapping between the fields of the old specification and the
-   *   fields of the new specification. An associative array, whose keys are
-   *   the fields of the new table, and values can take two possible forms:
-   *     - a simple string, which is interpreted as the name of a field of the
-   *       old table,
-   *     - an associative array with two keys 'expression' and 'arguments',
-   *       that will be used as an expression field.
-   */
-  protected function alterTable($table, $old_schema, $new_schema, array $mapping = array()) {
-    $i = 0;
-    do {
-      $new_table = $table . '_' . $i++;
-    } while ($this->tableExists($new_table));
-
-    $this->createTable($new_table, $new_schema);
-
-    // Build a SQL query to migrate the data from the old table to the new.
-    $select = $this->connection->select($table);
-
-    // Complete the mapping.
-    $possible_keys = array_keys($new_schema['fields']);
-    $mapping += array_combine($possible_keys, $possible_keys);
-
-    // Now add the fields.
-    foreach ($mapping as $field_alias => $field_source) {
-      // Just ignore this field (ie. use it's default value).
-      if (!isset($field_source)) {
-        continue;
-      }
-
-      if (is_array($field_source)) {
-        $select->addExpression($field_source['expression'], $field_alias, $field_source['arguments']);
-      }
-      else {
-        $select->addField($table, $field_source, $field_alias);
-      }
-    }
-
-    // Execute the data migration query.
-    $this->connection->insert($new_table)
-      ->from($select)
-      ->execute();
-
-    $old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField();
-    $new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField();
-    if ($old_count == $new_count) {
-      $this->dropTable($table);
-      $this->renameTable($new_table, $table);
-    }
-  }
-
-  /**
-   * Find out the schema of a table.
-   *
-   * This function uses introspection methods provided by the database to
-   * create a schema array. This is useful, for example, during update when
-   * the old schema is not available.
-   *
-   * @param $table
-   *   Name of the table.
-   * @return
-   *   An array representing the schema, from drupal_get_schema().
-   * @see drupal_get_schema()
-   */
-  protected function introspectSchema($table) {
-    $mapped_fields = array_flip($this->getFieldTypeMap());
-    $schema = array(
-      'fields' => array(),
-      'primary key' => array(),
-      'unique keys' => array(),
-      'indexes' => array(),
-    );
-
-    $info = $this->getPrefixInfo($table);
-    $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.table_info(' . $info['table'] . ')');
-    foreach ($result as $row) {
-      if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) {
-        $type = $matches[1];
-        $length = $matches[2];
-      }
-      else {
-        $type = $row->type;
-        $length = NULL;
-      }
-      if (isset($mapped_fields[$type])) {
-        list($type, $size) = explode(':', $mapped_fields[$type]);
-        $schema['fields'][$row->name] = array(
-          'type' => $type,
-          'size' => $size,
-          'not null' => !empty($row->notnull),
-          'default' => trim($row->dflt_value, "'"),
-        );
-        if ($length) {
-          $schema['fields'][$row->name]['length'] = $length;
-        }
-        if ($row->pk) {
-          $schema['primary key'][] = $row->name;
-        }
-      }
-      else {
-        new \Exception("Unable to parse the column type " . $row->type);
-      }
-    }
-    $indexes = array();
-    $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
-    foreach ($result as $row) {
-      if (strpos($row->name, 'sqlite_autoindex_') !== 0) {
-        $indexes[] = array(
-          'schema_key' => $row->unique ? 'unique keys' : 'indexes',
-          'name' => $row->name,
-        );
-      }
-    }
-    foreach ($indexes as $index) {
-      $name = $index['name'];
-      // Get index name without prefix.
-      $index_name = substr($name, strlen($info['table']) + 1);
-      $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $name . ')');
-      foreach ($result as $row) {
-        $schema[$index['schema_key']][$index_name][] = $row->name;
-      }
-    }
-    return $schema;
-  }
-
-  public function dropField($table, $field) {
-    if (!$this->fieldExists($table, $field)) {
-      return FALSE;
-    }
-
-    $old_schema = $this->introspectSchema($table);
-    $new_schema = $old_schema;
-
-    unset($new_schema['fields'][$field]);
-    foreach ($new_schema['indexes'] as $index => $fields) {
-      foreach ($fields as $key => $field_name) {
-        if ($field_name == $field) {
-          unset($new_schema['indexes'][$index][$key]);
-        }
-      }
-      // If this index has no more fields then remove it.
-      if (empty($new_schema['indexes'][$index])) {
-        unset($new_schema['indexes'][$index]);
-      }
-    }
-    $this->alterTable($table, $old_schema, $new_schema);
-    return TRUE;
-  }
-
-  public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
-    }
-    if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
-      throw new SchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
-    }
-
-    $old_schema = $this->introspectSchema($table);
-    $new_schema = $old_schema;
-
-    // Map the old field to the new field.
-    if ($field != $field_new) {
-      $mapping[$field_new] = $field;
-    }
-    else {
-      $mapping = array();
-    }
-
-    // Remove the previous definition and swap in the new one.
-    unset($new_schema['fields'][$field]);
-    $new_schema['fields'][$field_new] = $spec;
-
-    // Map the former indexes to the new column name.
-    $new_schema['primary key'] = $this->mapKeyDefinition($new_schema['primary key'], $mapping);
-    foreach (array('unique keys', 'indexes') as $k) {
-      foreach ($new_schema[$k] as &$key_definition) {
-        $key_definition = $this->mapKeyDefinition($key_definition, $mapping);
-      }
-    }
-
-    // Add in the keys from $keys_new.
-    if (isset($keys_new['primary key'])) {
-      $new_schema['primary key'] = $keys_new['primary key'];
-    }
-    foreach (array('unique keys', 'indexes') as $k) {
-      if (!empty($keys_new[$k])) {
-        $new_schema[$k] = $keys_new[$k] + $new_schema[$k];
-      }
-    }
-
-    $this->alterTable($table, $old_schema, $new_schema, $mapping);
-  }
-
-  /**
-   * Utility method: rename columns in an index definition according to a new mapping.
-   *
-   * @param $key_definition
-   *   The key definition.
-   * @param $mapping
-   *   The new mapping.
-   */
-  protected function mapKeyDefinition(array $key_definition, array $mapping) {
-    foreach ($key_definition as &$field) {
-      // The key definition can be an array($field, $length).
-      if (is_array($field)) {
-        $field = &$field[0];
-      }
-      if (isset($mapping[$field])) {
-        $field = $mapping[$field];
-      }
-    }
-    return $key_definition;
-  }
-
-  public function addIndex($table, $name, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
-    }
-    if ($this->indexExists($table, $name)) {
-      throw new SchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
-    }
-
-    $schema['indexes'][$name] = $fields;
-    $statements = $this->createIndexSql($table, $schema);
-    foreach ($statements as $statement) {
-      $this->connection->query($statement);
-    }
-  }
-
-  public function indexExists($table, $name) {
-    $info = $this->getPrefixInfo($table);
-
-    return $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $info['table'] . '_' . $name . ')')->fetchField() != '';
-  }
-
-  public function dropIndex($table, $name) {
-    if (!$this->indexExists($table, $name)) {
-      return FALSE;
-    }
-
-    $info = $this->getPrefixInfo($table);
-
-    $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
-    return TRUE;
-  }
-
-  public function addUniqueKey($table, $name, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
-    }
-    if ($this->indexExists($table, $name)) {
-      throw new SchemaObjectExistsException(t("Cannot add unique key @name to table @table: unique key already exists.", array('@table' => $table, '@name' => $name)));
-    }
-
-    $schema['unique keys'][$name] = $fields;
-    $statements = $this->createIndexSql($table, $schema);
-    foreach ($statements as $statement) {
-      $this->connection->query($statement);
-    }
-  }
-
-  public function dropUniqueKey($table, $name) {
-    if (!$this->indexExists($table, $name)) {
-      return FALSE;
-    }
-
-    $info = $this->getPrefixInfo($table);
-
-    $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
-    return TRUE;
-  }
-
-  public function addPrimaryKey($table, $fields) {
-    if (!$this->tableExists($table)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
-    }
-
-    $old_schema = $this->introspectSchema($table);
-    $new_schema = $old_schema;
-
-    if (!empty($new_schema['primary key'])) {
-      throw new SchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
-    }
-
-    $new_schema['primary key'] = $fields;
-    $this->alterTable($table, $old_schema, $new_schema);
-  }
-
-  public function dropPrimaryKey($table) {
-    $old_schema = $this->introspectSchema($table);
-    $new_schema = $old_schema;
-
-    if (empty($new_schema['primary key'])) {
-      return FALSE;
-    }
-
-    unset($new_schema['primary key']);
-    $this->alterTable($table, $old_schema, $new_schema);
-    return TRUE;
-  }
-
-  public function fieldSetDefault($table, $field, $default) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
-    }
-
-    $old_schema = $this->introspectSchema($table);
-    $new_schema = $old_schema;
-
-    $new_schema['fields'][$field]['default'] = $default;
-    $this->alterTable($table, $old_schema, $new_schema);
-  }
-
-  public function fieldSetNoDefault($table, $field) {
-    if (!$this->fieldExists($table, $field)) {
-      throw new SchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
-    }
-
-    $old_schema = $this->introspectSchema($table);
-    $new_schema = $old_schema;
-
-    unset($new_schema['fields'][$field]['default']);
-    $this->alterTable($table, $old_schema, $new_schema);
-  }
-
-  public function findTables($table_expression) {
-    // Don't add the prefix, $table_expression already includes the prefix.
-    $info = $this->getPrefixInfo($table_expression, FALSE);
-
-    // Can't use query placeholders for the schema because the query would have
-    // to be :prefixsqlite_master, which does not work.
-    $result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
-      ':type' => 'table',
-      ':table_name' => $info['table'],
-    ));
-    return $result->fetchAllKeyed(0, 0);
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php
deleted file mode 100644
index 5403611..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Select.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Select
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Query\Select as QuerySelect;
-
-class Select extends QuerySelect {
-  public function forUpdate($set = TRUE) {
-    // SQLite does not support FOR UPDATE so nothing to do.
-    return $this;
-  }
-}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php
deleted file mode 100644
index 0be036d..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Statement.php
+++ /dev/null
@@ -1,146 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Statement
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\StatementPrefetch;
-use Drupal\Core\Database\StatementInterface;
-
-/**
- * Specific SQLite implementation of DatabaseConnection.
- *
- * The PDO SQLite driver only closes SELECT statements when the PDOStatement
- * destructor is called and SQLite does not allow data change (INSERT,
- * UPDATE etc) on a table which has open SELECT statements. This is a
- * user-space mock of PDOStatement that buffers all the data and doesn't
- * have those limitations.
- */
-class Statement extends StatementPrefetch implements \Iterator, StatementInterface {
-
-  /**
-   * SQLite specific implementation of getStatement().
-   *
-   * The PDO SQLite layer doesn't replace numeric placeholders in queries
-   * correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
-   * fail. We replace numeric placeholders in the query ourselves to work
-   * around this bug.
-   *
-   * See http://bugs.php.net/bug.php?id=45259 for more details.
-   */
-  protected function getStatement($query, &$args = array()) {
-    if (count($args)) {
-      // Check if $args is a simple numeric array.
-      if (range(0, count($args) - 1) === array_keys($args)) {
-        // In that case, we have unnamed placeholders.
-        $count = 0;
-        $new_args = array();
-        foreach ($args as $value) {
-          if (is_float($value) || is_int($value)) {
-            if (is_float($value)) {
-              // Force the conversion to float so as not to loose precision
-              // in the automatic cast.
-              $value = sprintf('%F', $value);
-            }
-            $query = substr_replace($query, $value, strpos($query, '?'), 1);
-          }
-          else {
-            $placeholder = ':db_statement_placeholder_' . $count++;
-            $query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
-            $new_args[$placeholder] = $value;
-          }
-        }
-        $args = $new_args;
-      }
-      else {
-        // Else, this is using named placeholders.
-        foreach ($args as $placeholder => $value) {
-          if (is_float($value) || is_int($value)) {
-            if (is_float($value)) {
-              // Force the conversion to float so as not to loose precision
-              // in the automatic cast.
-              $value = sprintf('%F', $value);
-            }
-
-            // We will remove this placeholder from the query as PDO throws an
-            // exception if the number of placeholders in the query and the
-            // arguments does not match.
-            unset($args[$placeholder]);
-            // PDO allows placeholders to not be prefixed by a colon. See
-            // http://marc.info/?l=php-internals&m=111234321827149&w=2 for
-            // more.
-            if ($placeholder[0] != ':') {
-              $placeholder = ":$placeholder";
-            }
-            // When replacing the placeholders, make sure we search for the
-            // exact placeholder. For example, if searching for
-            // ':db_placeholder_1', do not replace ':db_placeholder_11'.
-            $query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
-          }
-        }
-      }
-    }
-
-    return $this->dbh->prepare($query);
-  }
-
-  public function execute($args = array(), $options = array()) {
-    try {
-      $return = parent::execute($args, $options);
-    }
-    catch (\PDOException $e) {
-      if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
-        // The schema has changed. SQLite specifies that we must resend the query.
-        $return = parent::execute($args, $options);
-      }
-      else {
-        // Rethrow the exception.
-        throw $e;
-      }
-    }
-
-    // In some weird cases, SQLite will prefix some column names by the name
-    // of the table. We post-process the data, by renaming the column names
-    // using the same convention as MySQL and PostgreSQL.
-    $rename_columns = array();
-    foreach ($this->columnNames as $k => $column) {
-      // In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
-      // instead of "field".
-      if (preg_match("/^\((.*)\)$/", $column, $matches)) {
-        $rename_columns[$column] = $matches[1];
-        $this->columnNames[$k] = $matches[1];
-        $column = $matches[1];
-      }
-
-      // Remove "table." prefixes.
-      if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
-        $rename_columns[$column] = $matches[1];
-        $this->columnNames[$k] = $matches[1];
-      }
-    }
-    if ($rename_columns) {
-      // DatabaseStatementPrefetch already extracted the first row,
-      // put it back into the result set.
-      if (isset($this->currentRow)) {
-        $this->data[0] = &$this->currentRow;
-      }
-
-      // Then rename all the columns across the result set.
-      foreach ($this->data as $k => $row) {
-        foreach ($rename_columns as $old_column => $new_column) {
-          $this->data[$k][$new_column] = $this->data[$k][$old_column];
-          unset($this->data[$k][$old_column]);
-        }
-      }
-
-      // Finally, extract the first row again.
-      $this->currentRow = $this->data[0];
-      unset($this->data[0]);
-    }
-
-    return $return;
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Transaction.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Transaction.php
deleted file mode 100644
index ae73ed8..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Transaction.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Transaction
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Transaction as DatabaseTransaction;
-
-class Transaction extends DatabaseTransaction { }
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php
deleted file mode 100644
index d06d27e..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Truncate.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Truncate
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Query\Truncate as QueryTruncate;
-
-/**
- * SQLite specific implementation of TruncateQuery.
- *
- * SQLite doesn't support TRUNCATE, but a DELETE query with no condition has
- * exactly the effect (it is implemented by DROPing the table).
- */
-class Truncate extends QueryTruncate {
-  public function __toString() {
-    // Create a sanitized comment string to prepend to the query.
-    $comments = $this->connection->makeComment($this->comments);
-
-    return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
-  }
-}
diff --git a/core/lib/Drupal/Core/Database/Driver/sqlite/Update.php b/core/lib/Drupal/Core/Database/Driver/sqlite/Update.php
deleted file mode 100644
index fa7d54b..0000000
--- a/core/lib/Drupal/Core/Database/Driver/sqlite/Update.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\Database\Driver\sqlite\Update
- */
-
-namespace Drupal\Core\Database\Driver\sqlite;
-
-use Drupal\Core\Database\Query\Update as QueryUpdate;
-
-class Update extends QueryUpdate { }
diff --git a/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php b/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php
index d5da27c..ffe041d 100644
--- a/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php
+++ b/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterIterator.php
@@ -44,6 +44,7 @@ class RecursiveExtensionFilterIterator extends \RecursiveFilterIterator {
    * @var array
    */
   protected $whitelist = array(
+    'drivers',
     'profiles',
     'modules',
     'themes',
diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index f51cbb3..39137fb 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -97,8 +97,8 @@ class ExtensionDiscovery {
    * compatible with the current version of Drupal core.
    *
    * @param string $type
-   *   The extension type to search for. One of 'profile', 'module', 'theme', or
-   *   'theme_engine'.
+   *   The extension type to search for. One of 'profile', 'module', 'theme',
+   *   'theme_engine', or 'driver'.
    * @param bool $include_tests
    *   (optional) Whether to explicitly include or exclude test extensions. By
    *   default, test extensions are only discovered when in a test environment.
diff --git a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
index 8cf65c9..f59c9b0 100644
--- a/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
+++ b/core/lib/Drupal/Core/Installer/Form/SiteSettingsForm.php
@@ -120,11 +120,17 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
     $driver = $form_state->getValue('driver');
     $database = $form_state->getValue($driver);
     $drivers = drupal_get_database_types();
+
     $reflection = new \ReflectionClass($drivers[$driver]);
-    $install_namespace = $reflection->getNamespaceName();
+
+    $database['driver'] = $driver;
+    // Record the effective filesystem path of this driver (without /Install).
+    $database['path'] = dirname(dirname($reflection->getFileName()));
+
     // Cut the trailing \Install from namespace.
+    // @todo 'namespace' is theoretically obsolete now?
+    $install_namespace = $reflection->getNamespaceName();
     $database['namespace'] = substr($install_namespace, 0, strrpos($install_namespace, '\\'));
-    $database['driver'] = $driver;
 
     $form_state->set('database', $database);
     $errors = install_database_errors($database, $form_state->getValue('settings_file'));
diff --git a/core/modules/comment/tests/src/Unit/CommentStatisticsUnitTest.php b/core/modules/comment/tests/src/Unit/CommentStatisticsUnitTest.php
index 6439fec..a8b9919 100644
--- a/core/modules/comment/tests/src/Unit/CommentStatisticsUnitTest.php
+++ b/core/modules/comment/tests/src/Unit/CommentStatisticsUnitTest.php
@@ -55,7 +55,7 @@ class CommentStatisticsUnitTest extends UnitTestCase {
    * Sets up required mocks and the CommentStatistics service under test.
    */
   protected function setUp() {
-    $this->statement = $this->getMockBuilder('Drupal\Core\Database\Driver\fake\FakeStatement')
+    $this->statement = $this->getMockBuilder('Drupal\fake\FakeStatement')
       ->disableOriginalConstructor()
       ->getMock();
 
diff --git a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
index 75348fa..27d8fad 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
@@ -8,7 +8,7 @@
 namespace Drupal\Tests\migrate\Unit;
 
 use Drupal\Tests\UnitTestCase;
-use Drupal\Core\Database\Driver\fake\FakeConnection;
+use Drupal\fake\FakeConnection;
 
 /**
  * Provides setup and helper methods for Migrate module tests.
@@ -63,7 +63,7 @@ protected function getMigration() {
    * @param string $prefix
    *   (optional) The table prefix on the database.
    *
-   * @return \Drupal\Core\Database\Driver\fake\FakeConnection
+   * @return \Drupal\fake\FakeConnection
    *   The database connection.
    */
   protected function getDatabase(array $database_contents, $connection_options = array(), $prefix = '') {
diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php
index e74dc24..ca61a6c 100644
--- a/core/tests/bootstrap.php
+++ b/core/tests/bootstrap.php
@@ -42,6 +42,7 @@ function drupal_phpunit_contrib_extension_directory_roots() {
     $path = "$sites_path/$site";
     $paths[] = is_dir("$path/modules") ? realpath("$path/modules") : NULL;
     $paths[] = is_dir("$path/profiles") ? realpath("$path/profiles") : NULL;
+    $paths[] = is_dir("$path/drivers") ? realpath("$path/drivers") : NULL;
   }
   return array_filter($paths);
 }
@@ -75,6 +76,7 @@ function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $l
 $extension_roots = array_merge(array(
   __DIR__ . '/../modules',
   __DIR__ . '/../profiles',
+  __DIR__ . '/../drivers',
 ), drupal_phpunit_contrib_extension_directory_roots());
 
 $dirs = array_map('drupal_phpunit_find_extension_directories', $extension_roots);
